Merge "Add KeepalivePacketData toString()"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 780ba26..302c0b3 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -19,6 +19,9 @@
       ]
     },
     {
+      "name": "netd_updatable_unit_test"
+    },
+    {
       "name": "TetheringTests"
     },
     {
@@ -40,6 +43,10 @@
       "name": "bpf_existence_test"
     },
     {
+      "name": "netd_updatable_unit_test",
+      "keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
+    },
+    {
       "name": "libclat_test"
     },
     {
@@ -63,6 +70,9 @@
       ]
     },
     {
+      "name": "netd_updatable_unit_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
+    },
+    {
       "name": "ConnectivityCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
     },
     {
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index c0e6749..46fd50f 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -65,6 +65,7 @@
 android_library {
     name: "TetheringApiCurrentLib",
     defaults: [
+        "ConnectivityNextEnableDefaults",
         "TetheringAndroidLibraryDefaults",
         "TetheringApiLevel"
     ],
@@ -98,7 +99,6 @@
     ],
     min_sdk_version: "30",
     header_libs: [
-        "bpf_syscall_wrappers",
         "bpf_connectivity_headers",
     ],
     srcs: [
@@ -111,7 +111,6 @@
     static_libs: [
         "libnet_utils_device_common_bpfjni",
         "libnetjniutils",
-        "libtcutils",
     ],
 
     // We cannot use plain "libc++" here to link libc++ dynamically because it results in:
@@ -159,7 +158,7 @@
 // Non-updatable tethering running in the system server process for devices not using the module
 android_app {
     name: "InProcessTethering",
-    defaults: ["TetheringAppDefaults", "TetheringApiLevel"],
+    defaults: ["TetheringAppDefaults", "TetheringApiLevel", "ConnectivityNextEnableDefaults"],
     static_libs: ["TetheringApiCurrentLib"],
     certificate: "platform",
     manifest: "AndroidManifest_InProcess.xml",
@@ -180,7 +179,7 @@
     // The permission configuration *must* be included to ensure security of the device
     required: [
         "NetworkPermissionConfig",
-        "privapp_whitelist_com.android.networkstack.tethering",
+        "privapp_allowlist_com.android.tethering",
     ],
     apex_available: ["com.android.tethering"],
     lint: { strict_updatability_linting: true },
@@ -200,7 +199,7 @@
     // The permission configuration *must* be included to ensure security of the device
     required: [
         "NetworkPermissionConfig",
-        "privapp_whitelist_com.android.networkstack.tethering",
+        "privapp_allowlist_com.android.tethering",
     ],
     apex_available: ["com.android.tethering"],
     lint: { strict_updatability_linting: true },
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 53a14ad..1a4ba9d 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -52,9 +52,9 @@
         first: {
             jni_libs: [
                 "libservice-connectivity",
-                "libcom_android_connectivity_com_android_net_module_util_jni",
-                "libtraffic_controller_jni",
+                "libandroid_net_connectivity_com_android_net_module_util_jni",
             ],
+            native_shared_libs: ["libnetd_updatable"],
         },
         both: {
             jni_libs: ["libframework-connectivity-jni"],
@@ -74,7 +74,10 @@
     apps: [
         "ServiceConnectivityResources",
     ],
-    prebuilts: ["current_sdkinfo"],
+    prebuilts: [
+        "current_sdkinfo",
+        "privapp_allowlist_com.android.tethering",
+    ],
     manifest: "manifest.json",
     key: "com.android.tethering.key",
     // Indicates that pre-installed version of this apex can be compressed.
diff --git a/Tethering/apex/canned_fs_config b/Tethering/apex/canned_fs_config
index 06e9617..5a03347 100644
--- a/Tethering/apex/canned_fs_config
+++ b/Tethering/apex/canned_fs_config
@@ -1,2 +1,2 @@
-/bin/for-system 0 1000 0550
+/bin/for-system 0 1000 0750
 /bin/for-system/clatd 1029 1029 06755
diff --git a/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt b/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt
index 88c77f2..c1d87bb 100644
--- a/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt
+++ b/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt
@@ -1,3 +1,216 @@
+Landroid/net/IIpSecService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Landroid/net/IIpSecService$Stub$Proxy;->addAddressToTunnelInterface(ILandroid/net/LinkAddress;Ljava/lang/String;)V
+Landroid/net/IIpSecService$Stub$Proxy;->allocateSecurityParameterIndex(Ljava/lang/String;ILandroid/os/IBinder;)Landroid/net/IpSecSpiResponse;
+Landroid/net/IIpSecService$Stub$Proxy;->applyTransportModeTransform(Landroid/os/ParcelFileDescriptor;II)V
+Landroid/net/IIpSecService$Stub$Proxy;->applyTunnelModeTransform(IIILjava/lang/String;)V
+Landroid/net/IIpSecService$Stub$Proxy;->closeUdpEncapsulationSocket(I)V
+Landroid/net/IIpSecService$Stub$Proxy;->createTransform(Landroid/net/IpSecConfig;Landroid/os/IBinder;Ljava/lang/String;)Landroid/net/IpSecTransformResponse;
+Landroid/net/IIpSecService$Stub$Proxy;->createTunnelInterface(Ljava/lang/String;Ljava/lang/String;Landroid/net/Network;Landroid/os/IBinder;Ljava/lang/String;)Landroid/net/IpSecTunnelInterfaceResponse;
+Landroid/net/IIpSecService$Stub$Proxy;->deleteTransform(I)V
+Landroid/net/IIpSecService$Stub$Proxy;->deleteTunnelInterface(ILjava/lang/String;)V
+Landroid/net/IIpSecService$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
+Landroid/net/IIpSecService$Stub$Proxy;->mRemote:Landroid/os/IBinder;
+Landroid/net/IIpSecService$Stub$Proxy;->openUdpEncapsulationSocket(ILandroid/os/IBinder;)Landroid/net/IpSecUdpEncapResponse;
+Landroid/net/IIpSecService$Stub$Proxy;->releaseSecurityParameterIndex(I)V
+Landroid/net/IIpSecService$Stub$Proxy;->removeAddressFromTunnelInterface(ILandroid/net/LinkAddress;Ljava/lang/String;)V
+Landroid/net/IIpSecService$Stub$Proxy;->removeTransportModeTransforms(Landroid/os/ParcelFileDescriptor;)V
+Landroid/net/IIpSecService$Stub;-><init>()V
+Landroid/net/IIpSecService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/IIpSecService;
+Landroid/net/IIpSecService$Stub;->DESCRIPTOR:Ljava/lang/String;
+Landroid/net/IIpSecService$Stub;->TRANSACTION_addAddressToTunnelInterface:I
+Landroid/net/IIpSecService$Stub;->TRANSACTION_allocateSecurityParameterIndex:I
+Landroid/net/IIpSecService$Stub;->TRANSACTION_applyTransportModeTransform:I
+Landroid/net/IIpSecService$Stub;->TRANSACTION_applyTunnelModeTransform:I
+Landroid/net/IIpSecService$Stub;->TRANSACTION_closeUdpEncapsulationSocket:I
+Landroid/net/IIpSecService$Stub;->TRANSACTION_createTransform:I
+Landroid/net/IIpSecService$Stub;->TRANSACTION_createTunnelInterface:I
+Landroid/net/IIpSecService$Stub;->TRANSACTION_deleteTransform:I
+Landroid/net/IIpSecService$Stub;->TRANSACTION_deleteTunnelInterface:I
+Landroid/net/IIpSecService$Stub;->TRANSACTION_openUdpEncapsulationSocket:I
+Landroid/net/IIpSecService$Stub;->TRANSACTION_releaseSecurityParameterIndex:I
+Landroid/net/IIpSecService$Stub;->TRANSACTION_removeAddressFromTunnelInterface:I
+Landroid/net/IIpSecService$Stub;->TRANSACTION_removeTransportModeTransforms:I
+Landroid/net/IIpSecService;->addAddressToTunnelInterface(ILandroid/net/LinkAddress;Ljava/lang/String;)V
+Landroid/net/IIpSecService;->allocateSecurityParameterIndex(Ljava/lang/String;ILandroid/os/IBinder;)Landroid/net/IpSecSpiResponse;
+Landroid/net/IIpSecService;->applyTransportModeTransform(Landroid/os/ParcelFileDescriptor;II)V
+Landroid/net/IIpSecService;->applyTunnelModeTransform(IIILjava/lang/String;)V
+Landroid/net/IIpSecService;->closeUdpEncapsulationSocket(I)V
+Landroid/net/IIpSecService;->createTransform(Landroid/net/IpSecConfig;Landroid/os/IBinder;Ljava/lang/String;)Landroid/net/IpSecTransformResponse;
+Landroid/net/IIpSecService;->createTunnelInterface(Ljava/lang/String;Ljava/lang/String;Landroid/net/Network;Landroid/os/IBinder;Ljava/lang/String;)Landroid/net/IpSecTunnelInterfaceResponse;
+Landroid/net/IIpSecService;->deleteTransform(I)V
+Landroid/net/IIpSecService;->deleteTunnelInterface(ILjava/lang/String;)V
+Landroid/net/IIpSecService;->openUdpEncapsulationSocket(ILandroid/os/IBinder;)Landroid/net/IpSecUdpEncapResponse;
+Landroid/net/IIpSecService;->releaseSecurityParameterIndex(I)V
+Landroid/net/IIpSecService;->removeAddressFromTunnelInterface(ILandroid/net/LinkAddress;Ljava/lang/String;)V
+Landroid/net/IIpSecService;->removeTransportModeTransforms(Landroid/os/ParcelFileDescriptor;)V
+Landroid/net/IpSecAlgorithm;->checkValidOrThrow(Ljava/lang/String;II)V
+Landroid/net/IpSecAlgorithm;->CRYPT_NULL:Ljava/lang/String;
+Landroid/net/IpSecAlgorithm;->equals(Landroid/net/IpSecAlgorithm;Landroid/net/IpSecAlgorithm;)Z
+Landroid/net/IpSecAlgorithm;->isAead()Z
+Landroid/net/IpSecAlgorithm;->isAuthentication()Z
+Landroid/net/IpSecAlgorithm;->isEncryption()Z
+Landroid/net/IpSecAlgorithm;->isUnsafeBuild()Z
+Landroid/net/IpSecAlgorithm;->mKey:[B
+Landroid/net/IpSecAlgorithm;->mName:Ljava/lang/String;
+Landroid/net/IpSecAlgorithm;->mTruncLenBits:I
+Landroid/net/IpSecAlgorithm;->TAG:Ljava/lang/String;
+Landroid/net/IpSecConfig;-><init>()V
+Landroid/net/IpSecConfig;-><init>(Landroid/net/IpSecConfig;)V
+Landroid/net/IpSecConfig;-><init>(Landroid/os/Parcel;)V
+Landroid/net/IpSecConfig;->CREATOR:Landroid/os/Parcelable$Creator;
+Landroid/net/IpSecConfig;->equals(Landroid/net/IpSecConfig;Landroid/net/IpSecConfig;)Z
+Landroid/net/IpSecConfig;->getAuthenticatedEncryption()Landroid/net/IpSecAlgorithm;
+Landroid/net/IpSecConfig;->getAuthentication()Landroid/net/IpSecAlgorithm;
+Landroid/net/IpSecConfig;->getDestinationAddress()Ljava/lang/String;
+Landroid/net/IpSecConfig;->getEncapRemotePort()I
+Landroid/net/IpSecConfig;->getEncapSocketResourceId()I
+Landroid/net/IpSecConfig;->getEncapType()I
+Landroid/net/IpSecConfig;->getEncryption()Landroid/net/IpSecAlgorithm;
+Landroid/net/IpSecConfig;->getMarkMask()I
+Landroid/net/IpSecConfig;->getMarkValue()I
+Landroid/net/IpSecConfig;->getMode()I
+Landroid/net/IpSecConfig;->getNattKeepaliveInterval()I
+Landroid/net/IpSecConfig;->getNetwork()Landroid/net/Network;
+Landroid/net/IpSecConfig;->getSourceAddress()Ljava/lang/String;
+Landroid/net/IpSecConfig;->getSpiResourceId()I
+Landroid/net/IpSecConfig;->mAuthenticatedEncryption:Landroid/net/IpSecAlgorithm;
+Landroid/net/IpSecConfig;->mAuthentication:Landroid/net/IpSecAlgorithm;
+Landroid/net/IpSecConfig;->mDestinationAddress:Ljava/lang/String;
+Landroid/net/IpSecConfig;->mEncapRemotePort:I
+Landroid/net/IpSecConfig;->mEncapSocketResourceId:I
+Landroid/net/IpSecConfig;->mEncapType:I
+Landroid/net/IpSecConfig;->mEncryption:Landroid/net/IpSecAlgorithm;
+Landroid/net/IpSecConfig;->mMarkMask:I
+Landroid/net/IpSecConfig;->mMarkValue:I
+Landroid/net/IpSecConfig;->mMode:I
+Landroid/net/IpSecConfig;->mNattKeepaliveInterval:I
+Landroid/net/IpSecConfig;->mNetwork:Landroid/net/Network;
+Landroid/net/IpSecConfig;->mSourceAddress:Ljava/lang/String;
+Landroid/net/IpSecConfig;->mSpiResourceId:I
+Landroid/net/IpSecConfig;->setAuthenticatedEncryption(Landroid/net/IpSecAlgorithm;)V
+Landroid/net/IpSecConfig;->setAuthentication(Landroid/net/IpSecAlgorithm;)V
+Landroid/net/IpSecConfig;->setDestinationAddress(Ljava/lang/String;)V
+Landroid/net/IpSecConfig;->setEncapRemotePort(I)V
+Landroid/net/IpSecConfig;->setEncapSocketResourceId(I)V
+Landroid/net/IpSecConfig;->setEncapType(I)V
+Landroid/net/IpSecConfig;->setEncryption(Landroid/net/IpSecAlgorithm;)V
+Landroid/net/IpSecConfig;->setMarkMask(I)V
+Landroid/net/IpSecConfig;->setMarkValue(I)V
+Landroid/net/IpSecConfig;->setMode(I)V
+Landroid/net/IpSecConfig;->setNattKeepaliveInterval(I)V
+Landroid/net/IpSecConfig;->setNetwork(Landroid/net/Network;)V
+Landroid/net/IpSecConfig;->setSourceAddress(Ljava/lang/String;)V
+Landroid/net/IpSecConfig;->setSpiResourceId(I)V
+Landroid/net/IpSecConfig;->TAG:Ljava/lang/String;
+Landroid/net/IpSecManager$IpSecTunnelInterface;-><init>(Landroid/content/Context;Landroid/net/IIpSecService;Ljava/net/InetAddress;Ljava/net/InetAddress;Landroid/net/Network;)V
+Landroid/net/IpSecManager$IpSecTunnelInterface;->addAddress(Ljava/net/InetAddress;I)V
+Landroid/net/IpSecManager$IpSecTunnelInterface;->getInterfaceName()Ljava/lang/String;
+Landroid/net/IpSecManager$IpSecTunnelInterface;->getResourceId()I
+Landroid/net/IpSecManager$IpSecTunnelInterface;->mCloseGuard:Ldalvik/system/CloseGuard;
+Landroid/net/IpSecManager$IpSecTunnelInterface;->mInterfaceName:Ljava/lang/String;
+Landroid/net/IpSecManager$IpSecTunnelInterface;->mLocalAddress:Ljava/net/InetAddress;
+Landroid/net/IpSecManager$IpSecTunnelInterface;->mOpPackageName:Ljava/lang/String;
+Landroid/net/IpSecManager$IpSecTunnelInterface;->mRemoteAddress:Ljava/net/InetAddress;
+Landroid/net/IpSecManager$IpSecTunnelInterface;->mResourceId:I
+Landroid/net/IpSecManager$IpSecTunnelInterface;->mService:Landroid/net/IIpSecService;
+Landroid/net/IpSecManager$IpSecTunnelInterface;->mUnderlyingNetwork:Landroid/net/Network;
+Landroid/net/IpSecManager$IpSecTunnelInterface;->removeAddress(Ljava/net/InetAddress;I)V
+Landroid/net/IpSecManager$ResourceUnavailableException;-><init>(Ljava/lang/String;)V
+Landroid/net/IpSecManager$SecurityParameterIndex;-><init>(Landroid/net/IIpSecService;Ljava/net/InetAddress;I)V
+Landroid/net/IpSecManager$SecurityParameterIndex;->getResourceId()I
+Landroid/net/IpSecManager$SecurityParameterIndex;->mCloseGuard:Ldalvik/system/CloseGuard;
+Landroid/net/IpSecManager$SecurityParameterIndex;->mDestinationAddress:Ljava/net/InetAddress;
+Landroid/net/IpSecManager$SecurityParameterIndex;->mResourceId:I
+Landroid/net/IpSecManager$SecurityParameterIndex;->mService:Landroid/net/IIpSecService;
+Landroid/net/IpSecManager$SecurityParameterIndex;->mSpi:I
+Landroid/net/IpSecManager$SpiUnavailableException;-><init>(Ljava/lang/String;I)V
+Landroid/net/IpSecManager$SpiUnavailableException;->mSpi:I
+Landroid/net/IpSecManager$Status;->OK:I
+Landroid/net/IpSecManager$Status;->RESOURCE_UNAVAILABLE:I
+Landroid/net/IpSecManager$Status;->SPI_UNAVAILABLE:I
+Landroid/net/IpSecManager$UdpEncapsulationSocket;-><init>(Landroid/net/IIpSecService;I)V
+Landroid/net/IpSecManager$UdpEncapsulationSocket;->getResourceId()I
+Landroid/net/IpSecManager$UdpEncapsulationSocket;->mCloseGuard:Ldalvik/system/CloseGuard;
+Landroid/net/IpSecManager$UdpEncapsulationSocket;->mPfd:Landroid/os/ParcelFileDescriptor;
+Landroid/net/IpSecManager$UdpEncapsulationSocket;->mPort:I
+Landroid/net/IpSecManager$UdpEncapsulationSocket;->mResourceId:I
+Landroid/net/IpSecManager$UdpEncapsulationSocket;->mService:Landroid/net/IIpSecService;
+Landroid/net/IpSecManager;-><init>(Landroid/content/Context;Landroid/net/IIpSecService;)V
+Landroid/net/IpSecManager;->applyTunnelModeTransform(Landroid/net/IpSecManager$IpSecTunnelInterface;ILandroid/net/IpSecTransform;)V
+Landroid/net/IpSecManager;->createIpSecTunnelInterface(Ljava/net/InetAddress;Ljava/net/InetAddress;Landroid/net/Network;)Landroid/net/IpSecManager$IpSecTunnelInterface;
+Landroid/net/IpSecManager;->INVALID_RESOURCE_ID:I
+Landroid/net/IpSecManager;->maybeHandleServiceSpecificException(Landroid/os/ServiceSpecificException;)V
+Landroid/net/IpSecManager;->mContext:Landroid/content/Context;
+Landroid/net/IpSecManager;->mService:Landroid/net/IIpSecService;
+Landroid/net/IpSecManager;->removeTunnelModeTransform(Landroid/net/Network;Landroid/net/IpSecTransform;)V
+Landroid/net/IpSecManager;->rethrowCheckedExceptionFromServiceSpecificException(Landroid/os/ServiceSpecificException;)Ljava/io/IOException;
+Landroid/net/IpSecManager;->rethrowUncheckedExceptionFromServiceSpecificException(Landroid/os/ServiceSpecificException;)Ljava/lang/RuntimeException;
+Landroid/net/IpSecManager;->TAG:Ljava/lang/String;
+Landroid/net/IpSecSpiResponse;-><init>(I)V
+Landroid/net/IpSecSpiResponse;-><init>(III)V
+Landroid/net/IpSecSpiResponse;-><init>(Landroid/os/Parcel;)V
+Landroid/net/IpSecSpiResponse;->CREATOR:Landroid/os/Parcelable$Creator;
+Landroid/net/IpSecSpiResponse;->resourceId:I
+Landroid/net/IpSecSpiResponse;->spi:I
+Landroid/net/IpSecSpiResponse;->status:I
+Landroid/net/IpSecSpiResponse;->TAG:Ljava/lang/String;
+Landroid/net/IpSecTransform$Builder;->buildTunnelModeTransform(Ljava/net/InetAddress;Landroid/net/IpSecManager$SecurityParameterIndex;)Landroid/net/IpSecTransform;
+Landroid/net/IpSecTransform$Builder;->mConfig:Landroid/net/IpSecConfig;
+Landroid/net/IpSecTransform$Builder;->mContext:Landroid/content/Context;
+Landroid/net/IpSecTransform$NattKeepaliveCallback;-><init>()V
+Landroid/net/IpSecTransform$NattKeepaliveCallback;->ERROR_HARDWARE_ERROR:I
+Landroid/net/IpSecTransform$NattKeepaliveCallback;->ERROR_HARDWARE_UNSUPPORTED:I
+Landroid/net/IpSecTransform$NattKeepaliveCallback;->ERROR_INVALID_NETWORK:I
+Landroid/net/IpSecTransform$NattKeepaliveCallback;->onError(I)V
+Landroid/net/IpSecTransform$NattKeepaliveCallback;->onStarted()V
+Landroid/net/IpSecTransform$NattKeepaliveCallback;->onStopped()V
+Landroid/net/IpSecTransform;-><init>(Landroid/content/Context;Landroid/net/IpSecConfig;)V
+Landroid/net/IpSecTransform;->activate()Landroid/net/IpSecTransform;
+Landroid/net/IpSecTransform;->checkResultStatus(I)V
+Landroid/net/IpSecTransform;->ENCAP_ESPINUDP:I
+Landroid/net/IpSecTransform;->ENCAP_ESPINUDP_NON_IKE:I
+Landroid/net/IpSecTransform;->ENCAP_NONE:I
+Landroid/net/IpSecTransform;->equals(Landroid/net/IpSecTransform;Landroid/net/IpSecTransform;)Z
+Landroid/net/IpSecTransform;->getConfig()Landroid/net/IpSecConfig;
+Landroid/net/IpSecTransform;->getIpSecService()Landroid/net/IIpSecService;
+Landroid/net/IpSecTransform;->getResourceId()I
+Landroid/net/IpSecTransform;->mCallbackHandler:Landroid/os/Handler;
+Landroid/net/IpSecTransform;->mCloseGuard:Ldalvik/system/CloseGuard;
+Landroid/net/IpSecTransform;->mConfig:Landroid/net/IpSecConfig;
+Landroid/net/IpSecTransform;->mContext:Landroid/content/Context;
+Landroid/net/IpSecTransform;->mKeepalive:Landroid/net/ConnectivityManager$PacketKeepalive;
+Landroid/net/IpSecTransform;->mKeepaliveCallback:Landroid/net/ConnectivityManager$PacketKeepaliveCallback;
+Landroid/net/IpSecTransform;->MODE_TRANSPORT:I
+Landroid/net/IpSecTransform;->MODE_TUNNEL:I
+Landroid/net/IpSecTransform;->mResourceId:I
+Landroid/net/IpSecTransform;->mUserKeepaliveCallback:Landroid/net/IpSecTransform$NattKeepaliveCallback;
+Landroid/net/IpSecTransform;->startNattKeepalive(Landroid/net/IpSecTransform$NattKeepaliveCallback;ILandroid/os/Handler;)V
+Landroid/net/IpSecTransform;->stopNattKeepalive()V
+Landroid/net/IpSecTransform;->TAG:Ljava/lang/String;
+Landroid/net/IpSecTransformResponse;-><init>(I)V
+Landroid/net/IpSecTransformResponse;-><init>(II)V
+Landroid/net/IpSecTransformResponse;-><init>(Landroid/os/Parcel;)V
+Landroid/net/IpSecTransformResponse;->CREATOR:Landroid/os/Parcelable$Creator;
+Landroid/net/IpSecTransformResponse;->resourceId:I
+Landroid/net/IpSecTransformResponse;->status:I
+Landroid/net/IpSecTransformResponse;->TAG:Ljava/lang/String;
+Landroid/net/IpSecTunnelInterfaceResponse;-><init>(I)V
+Landroid/net/IpSecTunnelInterfaceResponse;-><init>(IILjava/lang/String;)V
+Landroid/net/IpSecTunnelInterfaceResponse;-><init>(Landroid/os/Parcel;)V
+Landroid/net/IpSecTunnelInterfaceResponse;->CREATOR:Landroid/os/Parcelable$Creator;
+Landroid/net/IpSecTunnelInterfaceResponse;->interfaceName:Ljava/lang/String;
+Landroid/net/IpSecTunnelInterfaceResponse;->resourceId:I
+Landroid/net/IpSecTunnelInterfaceResponse;->status:I
+Landroid/net/IpSecTunnelInterfaceResponse;->TAG:Ljava/lang/String;
+Landroid/net/IpSecUdpEncapResponse;-><init>(I)V
+Landroid/net/IpSecUdpEncapResponse;-><init>(IIILjava/io/FileDescriptor;)V
+Landroid/net/IpSecUdpEncapResponse;-><init>(Landroid/os/Parcel;)V
+Landroid/net/IpSecUdpEncapResponse;->CREATOR:Landroid/os/Parcelable$Creator;
+Landroid/net/IpSecUdpEncapResponse;->fileDescriptor:Landroid/os/ParcelFileDescriptor;
+Landroid/net/IpSecUdpEncapResponse;->port:I
+Landroid/net/IpSecUdpEncapResponse;->resourceId:I
+Landroid/net/IpSecUdpEncapResponse;->status:I
+Landroid/net/IpSecUdpEncapResponse;->TAG:Ljava/lang/String;
 Landroid/net/nsd/DnsSdTxtRecord;-><init>()V
 Landroid/net/nsd/DnsSdTxtRecord;-><init>(Landroid/net/nsd/DnsSdTxtRecord;)V
 Landroid/net/nsd/DnsSdTxtRecord;-><init>([B)V
diff --git a/Tethering/apex/permissions/Android.bp b/Tethering/apex/permissions/Android.bp
new file mode 100644
index 0000000..ac9ec65
--- /dev/null
+++ b/Tethering/apex/permissions/Android.bp
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+    default_visibility: ["//packages/modules/Connectivity/Tethering:__subpackages__"],
+}
+
+prebuilt_etc {
+    name: "privapp_allowlist_com.android.tethering",
+    sub_dir: "permissions",
+    filename: "permissions.xml",
+    src: "permissions.xml",
+    installable: false,
+}
\ No newline at end of file
diff --git a/Tethering/apex/permissions/OWNERS b/Tethering/apex/permissions/OWNERS
new file mode 100644
index 0000000..8b7e2e5
--- /dev/null
+++ b/Tethering/apex/permissions/OWNERS
@@ -0,0 +1,2 @@
+per-file *.xml,OWNERS = set noparent
+per-file *.xml,OWNERS = file:platform/frameworks/base:/data/etc/OWNERS
diff --git a/Tethering/apex/permissions/permissions.xml b/Tethering/apex/permissions/permissions.xml
new file mode 100644
index 0000000..f26a961
--- /dev/null
+++ b/Tethering/apex/permissions/permissions.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+-->
+
+<permissions>
+    <privapp-permissions package="com.android.networkstack.tethering">
+        <permission name="android.permission.BLUETOOTH_PRIVILEGED" />
+        <permission name="android.permission.MANAGE_USB"/>
+        <permission name="android.permission.MODIFY_PHONE_STATE"/>
+        <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
+        <permission name="android.permission.TETHER_PRIVILEGED"/>
+        <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+        <permission name="android.permission.UPDATE_DEVICE_STATS"/>
+      </privapp-permissions>
+</permissions>
diff --git a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
index a33af61..26040a2 100644
--- a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
@@ -27,10 +27,10 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.net.module.util.bpf.Tether4Key;
+import com.android.net.module.util.bpf.Tether4Value;
 import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
 import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
-import com.android.networkstack.tethering.Tether4Key;
-import com.android.networkstack.tethering.Tether4Value;
 import com.android.networkstack.tethering.TetherStatsValue;
 
 import java.util.function.BiConsumer;
diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
index 7189933..e3b1539 100644
--- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
@@ -30,11 +30,11 @@
 import androidx.annotation.Nullable;
 
 import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.bpf.Tether4Key;
+import com.android.net.module.util.bpf.Tether4Value;
 import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
 import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
 import com.android.networkstack.tethering.BpfUtils;
-import com.android.networkstack.tethering.Tether4Key;
-import com.android.networkstack.tethering.Tether4Value;
 import com.android.networkstack.tethering.Tether6Value;
 import com.android.networkstack.tethering.TetherDevKey;
 import com.android.networkstack.tethering.TetherDevValue;
diff --git a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
index 08ab9ca..d663968 100644
--- a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
+++ b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
@@ -22,10 +22,10 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.net.module.util.bpf.Tether4Key;
+import com.android.net.module.util.bpf.Tether4Value;
 import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
 import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
-import com.android.networkstack.tethering.Tether4Key;
-import com.android.networkstack.tethering.Tether4Value;
 import com.android.networkstack.tethering.TetherStatsValue;
 
 import java.util.function.BiConsumer;
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index c82a993..9ca3f14 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -26,13 +26,18 @@
         // Using for test only
         "//cts/tests/netlegacy22.api",
         "//external/sl4a:__subpackages__",
+        "//frameworks/base/core/tests/bandwidthtests",
+        "//frameworks/base/core/tests/benchmarks",
+        "//frameworks/base/core/tests/utillib",
         "//frameworks/base/packages/Connectivity/tests:__subpackages__",
+        "//frameworks/base/tests/vcn",
         "//frameworks/libs/net/common/testutils",
         "//frameworks/libs/net/common/tests:__subpackages__",
         "//frameworks/opt/telephony/tests/telephonytests",
         "//packages/modules/CaptivePortalLogin/tests",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
         "//packages/modules/Connectivity/tests:__subpackages__",
+        "//packages/modules/IPsec/tests/iketests",
         "//packages/modules/NetworkStack/tests:__subpackages__",
         "//packages/modules/Wifi/service/tests/wifitests",
     ],
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 8c8a2fd..6550de2 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -52,6 +52,7 @@
 import android.system.OsConstants;
 import android.text.TextUtils;
 import android.util.ArraySet;
+import android.util.Base64;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -65,6 +66,8 @@
 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.bpf.Tether4Key;
+import com.android.net.module.util.bpf.Tether4Value;
 import com.android.net.module.util.netlink.ConntrackMessage;
 import com.android.net.module.util.netlink.NetlinkConstants;
 import com.android.net.module.util.netlink.NetlinkSocket;
@@ -116,6 +119,9 @@
     private static final String TETHER_ERROR_MAP_PATH = makeMapPath("error");
     private static final String TETHER_DEV_MAP_PATH = makeMapPath("dev");
 
+    // Using "," as a separator is safe because base64 characters are [0-9a-zA-Z/=+].
+    private static final String DUMP_BASE64_DELIMITER = ",";
+
     /** The names of all the BPF counters defined in bpf_tethering.h. */
     public static final String[] sBpfCounterNames = getBpfCounterNames();
 
@@ -1066,6 +1072,42 @@
         }
     }
 
+    private String ipv4RuleToBase64String(Tether4Key key, Tether4Value value) {
+        final byte[] keyBytes = key.writeToBytes();
+        final String keyBase64Str = Base64.encodeToString(keyBytes, Base64.DEFAULT)
+                .replace("\n", "");
+        final byte[] valueBytes = value.writeToBytes();
+        final String valueBase64Str = Base64.encodeToString(valueBytes, Base64.DEFAULT)
+                .replace("\n", "");
+
+        return keyBase64Str + DUMP_BASE64_DELIMITER + valueBase64Str;
+    }
+
+    private void dumpRawIpv4ForwardingRuleMap(
+            BpfMap<Tether4Key, Tether4Value> map, IndentingPrintWriter pw) throws ErrnoException {
+        if (map == null) {
+            pw.println("No IPv4 support");
+            return;
+        }
+        if (map.isEmpty()) {
+            pw.println("No rules");
+            return;
+        }
+        map.forEach((k, v) -> pw.println(ipv4RuleToBase64String(k, v)));
+    }
+
+    /**
+     * Dump raw BPF map in base64 encoded strings. For test only.
+     */
+    public void dumpRawMap(@NonNull IndentingPrintWriter pw) {
+        try (BpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map()) {
+            // TODO: dump downstream map.
+            dumpRawIpv4ForwardingRuleMap(upstreamMap, pw);
+        } catch (ErrnoException e) {
+            pw.println("Error dumping IPv4 map: " + e);
+        }
+    }
+
     private String l4protoToString(int proto) {
         if (proto == OsConstants.IPPROTO_TCP) {
             return "tcp";
diff --git a/Tethering/src/com/android/networkstack/tethering/Tether4Key.java b/Tethering/src/com/android/networkstack/tethering/Tether4Key.java
deleted file mode 100644
index a01ea34..0000000
--- a/Tethering/src/com/android/networkstack/tethering/Tether4Key.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.networkstack.tethering;
-
-import android.net.MacAddress;
-
-import androidx.annotation.NonNull;
-
-import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.Field;
-import com.android.net.module.util.Struct.Type;
-
-import java.net.Inet4Address;
-import java.net.UnknownHostException;
-import java.util.Objects;
-
-/** Key type for downstream & upstream IPv4 forwarding maps. */
-public class Tether4Key extends Struct {
-    @Field(order = 0, type = Type.U32)
-    public final long iif;
-
-    @Field(order = 1, type = Type.EUI48)
-    public final MacAddress dstMac;
-
-    @Field(order = 2, type = Type.U8, padding = 1)
-    public final short l4proto;
-
-    @Field(order = 3, type = Type.ByteArray, arraysize = 4)
-    public final byte[] src4;
-
-    @Field(order = 4, type = Type.ByteArray, arraysize = 4)
-    public final byte[] dst4;
-
-    @Field(order = 5, type = Type.UBE16)
-    public final int srcPort;
-
-    @Field(order = 6, type = Type.UBE16)
-    public final int dstPort;
-
-    public Tether4Key(final long iif, @NonNull final MacAddress dstMac, final short l4proto,
-            final byte[] src4, final byte[] dst4, final int srcPort,
-            final int dstPort) {
-        Objects.requireNonNull(dstMac);
-
-        this.iif = iif;
-        this.dstMac = dstMac;
-        this.l4proto = l4proto;
-        this.src4 = src4;
-        this.dst4 = dst4;
-        this.srcPort = srcPort;
-        this.dstPort = dstPort;
-    }
-
-    @Override
-    public String toString() {
-        try {
-            return String.format(
-                    "iif: %d, dstMac: %s, l4proto: %d, src4: %s, dst4: %s, "
-                            + "srcPort: %d, dstPort: %d",
-                    iif, dstMac, l4proto,
-                    Inet4Address.getByAddress(src4), Inet4Address.getByAddress(dst4),
-                    Short.toUnsignedInt((short) srcPort), Short.toUnsignedInt((short) dstPort));
-        } catch (UnknownHostException | IllegalArgumentException e) {
-            return String.format("Invalid IP address", e);
-        }
-    }
-}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tether4Value.java b/Tethering/src/com/android/networkstack/tethering/Tether4Value.java
deleted file mode 100644
index 03a226c..0000000
--- a/Tethering/src/com/android/networkstack/tethering/Tether4Value.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.networkstack.tethering;
-
-import android.net.MacAddress;
-
-import androidx.annotation.NonNull;
-
-import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.Field;
-import com.android.net.module.util.Struct.Type;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.Objects;
-
-/** Value type for downstream & upstream IPv4 forwarding maps. */
-public class Tether4Value extends Struct {
-    @Field(order = 0, type = Type.U32)
-    public final long oif;
-
-    // The ethhdr struct which is defined in uapi/linux/if_ether.h
-    @Field(order = 1, type = Type.EUI48)
-    public final MacAddress ethDstMac;
-    @Field(order = 2, type = Type.EUI48)
-    public final MacAddress ethSrcMac;
-    @Field(order = 3, type = Type.UBE16)
-    public final int ethProto;  // Packet type ID field.
-
-    @Field(order = 4, type = Type.U16)
-    public final int pmtu;
-
-    @Field(order = 5, type = Type.ByteArray, arraysize = 16)
-    public final byte[] src46;
-
-    @Field(order = 6, type = Type.ByteArray, arraysize = 16)
-    public final byte[] dst46;
-
-    @Field(order = 7, type = Type.UBE16)
-    public final int srcPort;
-
-    @Field(order = 8, type = Type.UBE16)
-    public final int dstPort;
-
-    // TODO: consider using U64.
-    @Field(order = 9, type = Type.U63)
-    public final long lastUsed;
-
-    public Tether4Value(final long oif, @NonNull final MacAddress ethDstMac,
-            @NonNull final MacAddress ethSrcMac, final int ethProto, final int pmtu,
-            final byte[] src46, final byte[] dst46, final int srcPort,
-            final int dstPort, final long lastUsed) {
-        Objects.requireNonNull(ethDstMac);
-        Objects.requireNonNull(ethSrcMac);
-
-        this.oif = oif;
-        this.ethDstMac = ethDstMac;
-        this.ethSrcMac = ethSrcMac;
-        this.ethProto = ethProto;
-        this.pmtu = pmtu;
-        this.src46 = src46;
-        this.dst46 = dst46;
-        this.srcPort = srcPort;
-        this.dstPort = dstPort;
-        this.lastUsed = lastUsed;
-    }
-
-    @Override
-    public String toString() {
-        try {
-            return String.format(
-                    "oif: %d, ethDstMac: %s, ethSrcMac: %s, ethProto: %d, pmtu: %d, "
-                            + "src46: %s, dst46: %s, srcPort: %d, dstPort: %d, "
-                            + "lastUsed: %d",
-                    oif, ethDstMac, ethSrcMac, ethProto, pmtu,
-                    InetAddress.getByAddress(src46), InetAddress.getByAddress(dst46),
-                    Short.toUnsignedInt((short) srcPort), Short.toUnsignedInt((short) dstPort),
-                    lastUsed);
-        } catch (UnknownHostException | IllegalArgumentException e) {
-            return String.format("Invalid IP address", e);
-        }
-    }
-}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index db9a64f..301a682 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -2479,6 +2479,13 @@
         @SuppressWarnings("resource") final IndentingPrintWriter pw = new IndentingPrintWriter(
                 writer, "  ");
 
+        // Used for testing instead of human debug.
+        // TODO: add options to choose which map to dump.
+        if (argsContain(args, "bpfRawMap")) {
+            mBpfCoordinator.dumpRawMap(pw);
+            return;
+        }
+
         if (argsContain(args, "bpf")) {
             dumpBpf(pw);
             return;
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/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 267c376..a3c46c2 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -101,11 +101,11 @@
 
 import com.android.net.module.util.BpfMap;
 import com.android.net.module.util.NetworkStackConstants;
+import com.android.net.module.util.bpf.Tether4Key;
+import com.android.net.module.util.bpf.Tether4Value;
 import com.android.networkstack.tethering.BpfCoordinator;
 import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
 import com.android.networkstack.tethering.PrivateAddressCoordinator;
-import com.android.networkstack.tethering.Tether4Key;
-import com.android.networkstack.tethering.Tether4Value;
 import com.android.networkstack.tethering.Tether6Value;
 import com.android.networkstack.tethering.TetherDevKey;
 import com.android.networkstack.tethering.TetherDevValue;
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..6c7a66d 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,8 @@
 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.bpf.Tether4Key;
+import com.android.net.module.util.bpf.Tether4Value;
 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 +110,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 +129,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 +155,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/bpf_progs/Android.bp b/bpf_progs/Android.bp
index cce2b71..6718402 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -42,6 +42,7 @@
         // TODO: remove it when NetworkStatsService is moved into the mainline module and no more
         // calls to JNI in libservices.core.
         "//frameworks/base/services/core/jni",
+        "//packages/modules/Connectivity/netd",
         "//packages/modules/Connectivity/service",
         "//packages/modules/Connectivity/service/native/libs/libclat",
         "//packages/modules/Connectivity/Tethering",
diff --git a/bpf_progs/bpf_shared.h b/bpf_progs/bpf_shared.h
index f0df97b..2ddc7b8 100644
--- a/bpf_progs/bpf_shared.h
+++ b/bpf_progs/bpf_shared.h
@@ -130,7 +130,8 @@
     STANDBY_MATCH = (1 << 3),
     POWERSAVE_MATCH = (1 << 4),
     RESTRICTED_MATCH = (1 << 5),
-    IIF_MATCH = (1 << 6),
+    LOW_POWER_STANDBY_MATCH = (1 << 6),
+    IIF_MATCH = (1 << 7),
 };
 
 enum BpfPermissionMatch {
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index f0af8b4..8d05757 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -210,6 +210,9 @@
         if ((enabledRules & RESTRICTED_MATCH) && !(uidRules & RESTRICTED_MATCH)) {
             return BPF_DROP;
         }
+        if ((enabledRules & LOW_POWER_STANDBY_MATCH) && !(uidRules & LOW_POWER_STANDBY_MATCH)) {
+            return BPF_DROP;
+        }
     }
     if (direction == BPF_INGRESS && (uidRules & IIF_MATCH)) {
         // Drops packets not coming from lo nor the allowlisted interface
@@ -294,12 +297,12 @@
     return match;
 }
 
-DEFINE_BPF_PROG("cgroupskb/ingress/stats", AID_ROOT, AID_ROOT, bpf_cgroup_ingress)
+DEFINE_BPF_PROG("cgroupskb/ingress/stats", AID_ROOT, AID_SYSTEM, bpf_cgroup_ingress)
 (struct __sk_buff* skb) {
     return bpf_traffic_account(skb, BPF_INGRESS);
 }
 
-DEFINE_BPF_PROG("cgroupskb/egress/stats", AID_ROOT, AID_ROOT, bpf_cgroup_egress)
+DEFINE_BPF_PROG("cgroupskb/egress/stats", AID_ROOT, AID_SYSTEM, bpf_cgroup_egress)
 (struct __sk_buff* skb) {
     return bpf_traffic_account(skb, BPF_EGRESS);
 }
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index abcfbeb..50a3eb2 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -26,46 +26,111 @@
 // The above defaults can be used to disable framework-connectivity t
 // targets while minimizing merge conflicts in the build rules.
 
-
-java_sdk_library {
-    name: "framework-connectivity-tiramisu",
+// SDK library for connectivity bootclasspath classes that were part of the non-updatable API before
+// T, and were moved to the module in T. Other bootclasspath classes in connectivity should go to
+// framework-connectivity.
+java_defaults {
+    name: "framework-connectivity-tiramisu-defaults",
     sdk_version: "module_current",
     min_sdk_version: "Tiramisu",
     defaults: [
         "framework-module-defaults",
-        "enable-framework-connectivity-t-targets",
     ],
     srcs: [
         ":framework-connectivity-tiramisu-updatable-sources",
+        ":framework-nearby-java-sources",
+    ],
+    stub_only_libs: [
+        // Use prebuilt framework-connectivity stubs to avoid circular dependencies
+        "sdk_module-lib_current_framework-connectivity",
     ],
     libs: [
         "unsupportedappusage",
         "app-compat-annotations",
+        "sdk_module-lib_current_framework-connectivity",
     ],
+    impl_only_libs: [
+        // The build system will use framework-bluetooth module_current stubs, because
+        // of sdk_version: "module_current" above.
+        "framework-bluetooth",
+        // Compile against the entire implementation of framework-connectivity,
+        // including hidden methods. This is safe because if framework-connectivity-t is
+        // on the bootclasspath (i.e., T), then framework-connectivity is also on the
+        // bootclasspath (because it shipped in S).
+        //
+        // This compiles against the pre-jarjar target so that this code can use
+        // non-jarjard names of widely-used packages such as com.android.net.module.util.
+        "framework-connectivity-pre-jarjar",
+    ],
+    aidl: {
+        generate_get_transaction_name: true,
+        include_dirs: [
+            // For connectivity-framework classes such as Network.aidl,
+            // and connectivity-framework-t classes such as
+            // NetworkStateSnapshot.aidl
+            "packages/modules/Connectivity/framework/aidl-export",
+        ],
+    },
+    apex_available: [
+        "com.android.tethering",
+    ],
+}
+
+java_library {
+    name: "framework-connectivity-tiramisu-pre-jarjar",
+    defaults: ["framework-connectivity-tiramisu-defaults"],
+    libs: [
+        "framework-bluetooth",
+        "framework-connectivity-pre-jarjar",
+    ],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
+// SDK library for connectivity bootclasspath classes that were part of the non-updatable API before
+// T, and were moved to the module in T. Other bootclasspath classes in connectivity should go to
+// framework-connectivity.
+java_sdk_library {
+    name: "framework-connectivity-tiramisu",
+    defaults: [
+        "framework-connectivity-tiramisu-defaults",
+        "enable-framework-connectivity-t-targets",
+    ],
+    // Do not add static_libs to this library: put them in framework-connectivity instead.
+    // The jarjar rules are only so that references to jarjared utils in
+    // framework-connectivity-pre-jarjar match at runtime.
+    jarjar_rules: ":connectivity-jarjar-rules",
     permitted_packages: [
         "android.net",
         "android.net.nsd",
-    ],
-    apex_available: [
-        "com.android.tethering",
+        "android.nearby",
+        "com.android.connectivity",
+        "com.android.nearby",
     ],
     impl_library_visibility: [
         "//packages/modules/Connectivity/Tethering/apex",
         // In preparation for future move
         "//packages/modules/Connectivity/apex",
         "//packages/modules/Connectivity/service-t",
+        "//packages/modules/Nearby/service",
         "//frameworks/base",
 
         // Tests using hidden APIs
         "//cts/tests/netlegacy22.api",
+        "//cts/tests/tests/app.usage", // NetworkUsageStatsTest
         "//external/sl4a:__subpackages__",
+        "//frameworks/base/core/tests/bandwidthtests",
+        "//frameworks/base/core/tests/benchmarks",
+        "//frameworks/base/core/tests/utillib",
+        "//frameworks/base/tests/vcn",
         "//frameworks/libs/net/common/testutils",
         "//frameworks/libs/net/common/tests:__subpackages__",
         "//frameworks/opt/telephony/tests/telephonytests",
         "//packages/modules/CaptivePortalLogin/tests",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
         "//packages/modules/Connectivity/tests:__subpackages__",
+        "//packages/modules/IPsec/tests/iketests",
         "//packages/modules/NetworkStack/tests:__subpackages__",
+        "//packages/modules/Nearby/tests:__subpackages__",
         "//packages/modules/Wifi/service/tests/wifitests",
     ],
 }
diff --git a/framework-t/api/OWNERS b/framework-t/api/OWNERS
new file mode 100644
index 0000000..de0f905
--- /dev/null
+++ b/framework-t/api/OWNERS
@@ -0,0 +1 @@
+file:platform/packages/modules/Connectivity:master:/nearby/OWNERS
diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt
index 0443456..1389ff2 100644
--- a/framework-t/api/current.txt
+++ b/framework-t/api/current.txt
@@ -1,8 +1,83 @@
 // Signature format: 2.0
+package android.net {
+
+  public final class IpSecAlgorithm implements android.os.Parcelable {
+    ctor public IpSecAlgorithm(@NonNull String, @NonNull byte[]);
+    ctor public IpSecAlgorithm(@NonNull String, @NonNull byte[], int);
+    method public int describeContents();
+    method @NonNull public byte[] getKey();
+    method @NonNull public String getName();
+    method @NonNull public static java.util.Set<java.lang.String> getSupportedAlgorithms();
+    method public int getTruncationLengthBits();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final String AUTH_AES_CMAC = "cmac(aes)";
+    field public static final String AUTH_AES_XCBC = "xcbc(aes)";
+    field public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
+    field public static final String AUTH_CRYPT_CHACHA20_POLY1305 = "rfc7539esp(chacha20,poly1305)";
+    field public static final String AUTH_HMAC_MD5 = "hmac(md5)";
+    field public static final String AUTH_HMAC_SHA1 = "hmac(sha1)";
+    field public static final String AUTH_HMAC_SHA256 = "hmac(sha256)";
+    field public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
+    field public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
+    field public static final String CRYPT_AES_CBC = "cbc(aes)";
+    field public static final String CRYPT_AES_CTR = "rfc3686(ctr(aes))";
+  }
+
+  public class IpSecManager {
+    method @NonNull public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(@NonNull java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
+    method @NonNull public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(@NonNull java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+    method public void applyTransportModeTransform(@NonNull java.net.Socket, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
+    method public void applyTransportModeTransform(@NonNull java.net.DatagramSocket, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
+    method public void applyTransportModeTransform(@NonNull java.io.FileDescriptor, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
+    method @NonNull public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+    method @NonNull public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+    method public void removeTransportModeTransforms(@NonNull java.net.Socket) throws java.io.IOException;
+    method public void removeTransportModeTransforms(@NonNull java.net.DatagramSocket) throws java.io.IOException;
+    method public void removeTransportModeTransforms(@NonNull java.io.FileDescriptor) throws java.io.IOException;
+    field public static final int DIRECTION_IN = 0; // 0x0
+    field public static final int DIRECTION_OUT = 1; // 0x1
+  }
+
+  public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
+  }
+
+  public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
+    method public void close();
+    method public int getSpi();
+  }
+
+  public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException {
+    method public int getSpi();
+  }
+
+  public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
+    method public void close() throws java.io.IOException;
+    method public java.io.FileDescriptor getFileDescriptor();
+    method public int getPort();
+  }
+
+  public final class IpSecTransform implements java.lang.AutoCloseable {
+    method public void close();
+  }
+
+  public static class IpSecTransform.Builder {
+    ctor public IpSecTransform.Builder(@NonNull android.content.Context);
+    method @NonNull public android.net.IpSecTransform buildTransportModeTransform(@NonNull java.net.InetAddress, @NonNull android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+    method @NonNull public android.net.IpSecTransform.Builder setAuthenticatedEncryption(@NonNull android.net.IpSecAlgorithm);
+    method @NonNull public android.net.IpSecTransform.Builder setAuthentication(@NonNull android.net.IpSecAlgorithm);
+    method @NonNull public android.net.IpSecTransform.Builder setEncryption(@NonNull android.net.IpSecAlgorithm);
+    method @NonNull public android.net.IpSecTransform.Builder setIpv4Encapsulation(@NonNull android.net.IpSecManager.UdpEncapsulationSocket, int);
+  }
+
+}
+
 package android.net.nsd {
 
   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 +118,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-t/api/lint-baseline.txt b/framework-t/api/lint-baseline.txt
new file mode 100644
index 0000000..1e30747
--- /dev/null
+++ b/framework-t/api/lint-baseline.txt
@@ -0,0 +1,7 @@
+// Baseline format: 1.0
+GenericException: android.net.IpSecManager.SecurityParameterIndex#finalize():
+    Methods must not throw generic exceptions (`java.lang.Throwable`)
+GenericException: android.net.IpSecManager.UdpEncapsulationSocket#finalize():
+    Methods must not throw generic exceptions (`java.lang.Throwable`)
+GenericException: android.net.IpSecTransform#finalize():
+    Methods must not throw generic exceptions (`java.lang.Throwable`)
diff --git a/framework-t/api/module-lib-current.txt b/framework-t/api/module-lib-current.txt
index 81d89c6..2ff3451 100644
--- a/framework-t/api/module-lib-current.txt
+++ b/framework-t/api/module-lib-current.txt
@@ -5,5 +5,13 @@
     method public static void registerServiceWrappers();
   }
 
+  public class IpSecManager {
+    field public static final int DIRECTION_FWD = 2; // 0x2
+  }
+
+  public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
+    method public int getResourceId();
+  }
+
 }
 
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index d802177..041bcaf 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -1 +1,22 @@
 // Signature format: 2.0
+package android.net {
+
+  public class IpSecManager {
+    method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void applyTunnelModeTransform(@NonNull android.net.IpSecManager.IpSecTunnelInterface, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecManager.IpSecTunnelInterface createIpSecTunnelInterface(@NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull android.net.Network) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+  }
+
+  public static final class IpSecManager.IpSecTunnelInterface implements java.lang.AutoCloseable {
+    method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void addAddress(@NonNull java.net.InetAddress, int) throws java.io.IOException;
+    method public void close();
+    method @NonNull public String getInterfaceName();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void removeAddress(@NonNull java.net.InetAddress, int) throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void setUnderlyingNetwork(@NonNull android.net.Network) throws java.io.IOException;
+  }
+
+  public static class IpSecTransform.Builder {
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecTransform buildTunnelModeTransform(@NonNull java.net.InetAddress, @NonNull android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+  }
+
+}
+
diff --git a/framework-t/api/system-lint-baseline.txt b/framework-t/api/system-lint-baseline.txt
new file mode 100644
index 0000000..9baf991
--- /dev/null
+++ b/framework-t/api/system-lint-baseline.txt
@@ -0,0 +1,7 @@
+// Baseline format: 1.0
+BuilderSetStyle: android.net.IpSecTransform.Builder#buildTunnelModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex):
+    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.IpSecTransform.Builder.buildTunnelModeTransform(java.net.InetAddress,android.net.IpSecManager.SecurityParameterIndex)
+
+
+GenericException: android.net.IpSecManager.IpSecTunnelInterface#finalize():
+    Methods must not throw generic exceptions (`java.lang.Throwable`)
diff --git a/framework/Android.bp b/framework/Android.bp
index de505c7..da16a8d 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -55,12 +55,11 @@
     ],
 }
 
-java_sdk_library {
-    name: "framework-connectivity",
+java_defaults {
+    name: "framework-connectivity-defaults",
+    defaults: ["framework-module-defaults"],
     sdk_version: "module_current",
     min_sdk_version: "30",
-    defaults: ["framework-module-defaults"],
-    installable: true,
     srcs: [
         ":framework-connectivity-sources",
         ":net-utils-framework-common-srcs",
@@ -76,6 +75,9 @@
             "frameworks/native/aidl/binder", // For PersistableBundle.aidl
         ],
     },
+    stub_only_libs: [
+        "framework-connectivity-tiramisu.stubs.module_lib",
+    ],
     impl_only_libs: [
         "framework-tethering.stubs.module_lib",
         "framework-wifi.stubs.module_lib",
@@ -83,16 +85,42 @@
     ],
     static_libs: [
         "modules-utils-build",
+        "modules-utils-preconditions",
     ],
     libs: [
+        "framework-connectivity-tiramisu.stubs.module_lib",
         "unsupportedappusage",
     ],
-    jarjar_rules: "jarjar-rules.txt",
+    apex_available: [
+        "com.android.tethering",
+    ],
+    lint: { strict_updatability_linting: true },
+}
+
+java_library {
+    name: "framework-connectivity-pre-jarjar",
+    defaults: ["framework-connectivity-defaults"],
+    libs: [
+        // This cannot be in the defaults clause above because if it were, it would be used
+        // to generate the connectivity stubs. That would create a circular dependency
+        // because the tethering stubs depend on the connectivity stubs (e.g.,
+        // TetheringRequest depends on LinkAddress).
+        "framework-tethering.stubs.module_lib",
+    ],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"]
+}
+
+java_sdk_library {
+    name: "framework-connectivity",
+    defaults: ["framework-connectivity-defaults"],
+    installable: true,
+    jarjar_rules: ":connectivity-jarjar-rules",
     permitted_packages: ["android.net"],
     impl_library_visibility: [
         "//packages/modules/Connectivity/Tethering/apex",
         // In preparation for future move
         "//packages/modules/Connectivity/apex",
+        "//packages/modules/Connectivity/framework-t",
         "//packages/modules/Connectivity/service",
         "//packages/modules/Connectivity/service-t",
         "//frameworks/base/packages/Connectivity/service",
@@ -100,21 +128,23 @@
 
         // Tests using hidden APIs
         "//cts/tests/netlegacy22.api",
+        "//cts/tests/tests/app.usage", // NetworkUsageStatsTest
         "//external/sl4a:__subpackages__",
         "//frameworks/base/packages/Connectivity/tests:__subpackages__",
+        "//frameworks/base/core/tests/bandwidthtests",
+        "//frameworks/base/core/tests/benchmarks",
+        "//frameworks/base/core/tests/utillib",
+        "//frameworks/base/tests/vcn",
         "//frameworks/libs/net/common/testutils",
         "//frameworks/libs/net/common/tests:__subpackages__",
         "//frameworks/opt/telephony/tests/telephonytests",
         "//packages/modules/CaptivePortalLogin/tests",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
         "//packages/modules/Connectivity/tests:__subpackages__",
+        "//packages/modules/IPsec/tests/iketests",
         "//packages/modules/NetworkStack/tests:__subpackages__",
         "//packages/modules/Wifi/service/tests/wifitests",
     ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-    lint: { strict_updatability_linting: true },
 }
 
 cc_library_shared {
diff --git a/framework/aidl-export/android/net/NetworkAgentConfig.aidl b/framework/aidl-export/android/net/NetworkAgentConfig.aidl
index cb70bdd..02d50b7 100644
--- a/framework/aidl-export/android/net/NetworkAgentConfig.aidl
+++ b/framework/aidl-export/android/net/NetworkAgentConfig.aidl
@@ -16,4 +16,4 @@
 
 package android.net;
 
-parcelable NetworkAgentConfig;
+@JavaOnlyStableParcelable parcelable NetworkAgentConfig;
diff --git a/framework/aidl-export/android/net/NetworkStateSnapshot.aidl b/framework/aidl-export/android/net/NetworkStateSnapshot.aidl
new file mode 100644
index 0000000..cb602d7
--- /dev/null
+++ b/framework/aidl-export/android/net/NetworkStateSnapshot.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+parcelable NetworkStateSnapshot;
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 8da421d..5579db6 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -10,6 +10,8 @@
     method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List<android.net.NetworkStateSnapshot> getAllNetworkStateSnapshots();
     method @Nullable public android.net.ProxyInfo getGlobalProxy();
     method @NonNull public static android.util.Range<java.lang.Integer> getIpSecNetIdRange();
+    method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.LinkProperties redactLinkPropertiesForPackage(@NonNull android.net.LinkProperties, int, @NonNull String);
+    method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.NetworkCapabilities redactNetworkCapabilitiesForPackage(@NonNull android.net.NetworkCapabilities, int, @NonNull String);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackForUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void replaceFirewallChain(int, @NonNull int[]);
@@ -43,9 +45,11 @@
     field public static final int BLOCKED_REASON_BATTERY_SAVER = 1; // 0x1
     field public static final int BLOCKED_REASON_DOZE = 2; // 0x2
     field public static final int BLOCKED_REASON_LOCKDOWN_VPN = 16; // 0x10
+    field public static final int BLOCKED_REASON_LOW_POWER_STANDBY = 32; // 0x20
     field public static final int BLOCKED_REASON_NONE = 0; // 0x0
     field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8
     field public static final int FIREWALL_CHAIN_DOZABLE = 1; // 0x1
+    field public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5; // 0x5
     field public static final int FIREWALL_CHAIN_POWERSAVE = 3; // 0x3
     field public static final int FIREWALL_CHAIN_RESTRICTED = 4; // 0x4
     field public static final int FIREWALL_CHAIN_STANDBY = 2; // 0x2
@@ -67,6 +71,7 @@
     method @NonNull public static java.time.Duration getDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
     method public static int getDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, int);
     method @Nullable public static android.net.ProxyInfo getGlobalProxy(@NonNull android.content.Context);
+    method public static long getIngressRateLimitInBytesPerSecond(@NonNull android.content.Context);
     method @NonNull public static java.time.Duration getMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
     method public static boolean getMobileDataAlwaysOn(@NonNull android.content.Context, boolean);
     method @NonNull public static java.util.Set<java.lang.Integer> getMobileDataPreferredUids(@NonNull android.content.Context);
@@ -87,6 +92,7 @@
     method public static void setDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
     method public static void setDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, @IntRange(from=0, to=100) int);
     method public static void setGlobalProxy(@NonNull android.content.Context, @NonNull android.net.ProxyInfo);
+    method public static void setIngressRateLimitInBytesPerSecond(@NonNull android.content.Context, @IntRange(from=0xffffffff, to=java.lang.Integer.MAX_VALUE) long);
     method public static void setMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
     method public static void setMobileDataAlwaysOn(@NonNull android.content.Context, boolean);
     method public static void setMobileDataPreferredUids(@NonNull android.content.Context, @NonNull java.util.Set<java.lang.Integer>);
@@ -122,16 +128,19 @@
 
   public final class NetworkAgentConfig implements android.os.Parcelable {
     method @Nullable public String getSubscriberId();
+    method public boolean getVpnRequiresValidation();
     method public boolean isBypassableVpn();
   }
 
   public static final class NetworkAgentConfig.Builder {
     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 setLocalRoutesExcludedForVpn(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 {
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public java.util.Set<java.lang.Integer> getAccessUids();
     method @Nullable public java.util.Set<android.util.Range<java.lang.Integer>> getUids();
     method public boolean hasForbiddenCapability(int);
     field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL
@@ -143,6 +152,7 @@
   }
 
   public static final class NetworkCapabilities.Builder {
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAccessUids(@NonNull java.util.Set<java.lang.Integer>);
     method @NonNull public android.net.NetworkCapabilities.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
   }
 
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/jarjar-rules.txt b/framework/jarjar-rules.txt
deleted file mode 100644
index ae6b9c3..0000000
--- a/framework/jarjar-rules.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-rule com.android.net.module.util.** android.net.connectivity.framework.util.@1
-rule com.android.modules.utils.** android.net.connectivity.framework.modules.utils.@1
-rule android.net.NetworkFactory* android.net.connectivity.framework.NetworkFactory@1
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 7593482..e8e1efa 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -877,6 +877,15 @@
     public static final int BLOCKED_REASON_LOCKDOWN_VPN = 1 << 4;
 
     /**
+     * Flag to indicate that an app is subject to Low Power Standby restrictions that would
+     * result in its network access being blocked.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int BLOCKED_REASON_LOW_POWER_STANDBY = 1 << 5;
+
+    /**
      * Flag to indicate that an app is subject to Data saver restrictions that would
      * result in its metered network access being blocked.
      *
@@ -914,6 +923,7 @@
             BLOCKED_REASON_APP_STANDBY,
             BLOCKED_REASON_RESTRICTED_MODE,
             BLOCKED_REASON_LOCKDOWN_VPN,
+            BLOCKED_REASON_LOW_POWER_STANDBY,
             BLOCKED_METERED_REASON_DATA_SAVER,
             BLOCKED_METERED_REASON_USER_RESTRICTED,
             BLOCKED_METERED_REASON_ADMIN_DISABLED,
@@ -931,6 +941,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     private final IConnectivityManager mService;
 
+    // LINT.IfChange(firewall_chain)
     /**
      * Firewall chain for device idle (doze mode).
      * Allowlist of apps that have network access in device idle.
@@ -963,15 +974,25 @@
     @SystemApi(client = MODULE_LIBRARIES)
     public static final int FIREWALL_CHAIN_RESTRICTED = 4;
 
+    /**
+     * Firewall chain used for low power standby.
+     * Allowlist of apps that have access in low power standby.
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = false, prefix = "FIREWALL_CHAIN_", value = {
         FIREWALL_CHAIN_DOZABLE,
         FIREWALL_CHAIN_STANDBY,
         FIREWALL_CHAIN_POWERSAVE,
-        FIREWALL_CHAIN_RESTRICTED
+        FIREWALL_CHAIN_RESTRICTED,
+        FIREWALL_CHAIN_LOW_POWER_STANDBY
     })
     public @interface FirewallChain {}
+    // LINT.ThenChange(packages/modules/Connectivity/service/native/include/Common.h)
 
     /**
      * A kludge to facilitate static access where a Context pointer isn't available, like in the
@@ -1604,16 +1625,45 @@
     }
 
     /**
-     * Get the {@link NetworkCapabilities} for the given {@link Network}.  This
-     * will return {@code null} if the network is unknown or if the |network| argument is null.
+     * Redact {@link LinkProperties} for a given package
      *
-     * This will remove any location sensitive data in {@link TransportInfo} embedded in
-     * {@link NetworkCapabilities#getTransportInfo()}. Some transport info instances like
-     * {@link android.net.wifi.WifiInfo} contain location sensitive information. Retrieving
-     * this location sensitive information (subject to app's location permissions) will be
-     * noted by system. To include any location sensitive data in {@link TransportInfo},
-     * use a {@link NetworkCallback} with
-     * {@link NetworkCallback#FLAG_INCLUDE_LOCATION_INFO} flag.
+     * Returns an instance of the given {@link LinkProperties} appropriately redacted to send to the
+     * given package, considering its permissions.
+     *
+     * @param lp A {@link LinkProperties} which will be redacted.
+     * @param uid The target uid.
+     * @param packageName The name of the package, for appops logging.
+     * @return A redacted {@link LinkProperties} which is appropriate to send to the given uid,
+     *         or null if the uid lacks the ACCESS_NETWORK_STATE permission.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETTINGS})
+    @SystemApi(client = MODULE_LIBRARIES)
+    @Nullable
+    public LinkProperties redactLinkPropertiesForPackage(@NonNull LinkProperties lp, int uid,
+            @NonNull String packageName) {
+        try {
+            return mService.redactLinkPropertiesForPackage(
+                    lp, uid, packageName, getAttributionTag());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the {@link NetworkCapabilities} for the given {@link Network}, or null.
+     *
+     * This will remove any location sensitive data in the returned {@link NetworkCapabilities}.
+     * Some {@link TransportInfo} instances like {@link android.net.wifi.WifiInfo} contain location
+     * sensitive information. To retrieve this location sensitive information (subject to
+     * the caller's location permissions), use a {@link NetworkCallback} with the
+     * {@link NetworkCallback#FLAG_INCLUDE_LOCATION_INFO} flag instead.
+     *
+     * This method returns {@code null} if the network is unknown or if the |network| argument
+     * is null.
      *
      * @param network The {@link Network} object identifying the network in question.
      * @return The {@link NetworkCapabilities} for the network, or {@code null}.
@@ -1630,6 +1680,38 @@
     }
 
     /**
+     * Redact {@link NetworkCapabilities} for a given package.
+     *
+     * Returns an instance of {@link NetworkCapabilities} that is appropriately redacted to send
+     * to the given package, considering its permissions. Calling this method will blame the UID for
+     * retrieving the device location if the passed capabilities contain location-sensitive
+     * information.
+     *
+     * @param nc A {@link NetworkCapabilities} instance which will be redacted.
+     * @param uid The target uid.
+     * @param packageName The name of the package, for appops logging.
+     * @return A redacted {@link NetworkCapabilities} which is appropriate to send to the given uid,
+     *         or null if the uid lacks the ACCESS_NETWORK_STATE permission.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETTINGS})
+    @SystemApi(client = MODULE_LIBRARIES)
+    @Nullable
+    public NetworkCapabilities redactNetworkCapabilitiesForPackage(
+            @NonNull NetworkCapabilities nc,
+            int uid, @NonNull String packageName) {
+        try {
+            return mService.redactNetworkCapabilitiesForPackage(nc, uid, packageName,
+                    getAttributionTag());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets a URL that can be used for resolving whether a captive portal is present.
      * 1. This URL should respond with a 204 response to a GET request to indicate no captive
      *    portal is present.
@@ -3526,7 +3608,20 @@
          * @hide
          */
         public static final int FLAG_NONE = 0;
+
         /**
+         * Inclusion of this flag means location-sensitive redaction requests keeping location info.
+         *
+         * Some objects like {@link NetworkCapabilities} may contain location-sensitive information.
+         * Prior to Android 12, this information is always returned to apps holding the appropriate
+         * permission, possibly noting that the app has used location.
+         * <p>In Android 12 and above, by default the sent objects do not contain any location
+         * information, even if the app holds the necessary permissions, and the system does not
+         * take note of location usage by the app. Apps can request that location information is
+         * included, in which case the system will check location permission and the location
+         * toggle state, and take note of location usage by the app if any such information is
+         * returned.
+         *
          * Use this flag to include any location sensitive data in {@link NetworkCapabilities} sent
          * via {@link #onCapabilitiesChanged(Network, NetworkCapabilities)}.
          * <p>
@@ -3543,8 +3638,7 @@
          * <li> Retrieving this location sensitive information (subject to app's location
          * permissions) will be noted by system. </li>
          * <li> Without this flag any {@link NetworkCapabilities} provided via the callback does
-         * not include location sensitive info.
-         * </p>
+         * not include location sensitive information.
          */
         // Note: Some existing fields which are location sensitive may still be included without
         // this flag if the app targets SDK < S (to maintain backwards compatibility).
diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java
index 8fc0065..4e28b29 100644
--- a/framework/src/android/net/ConnectivitySettingsManager.java
+++ b/framework/src/android/net/ConnectivitySettingsManager.java
@@ -384,6 +384,14 @@
             "uids_allowed_on_restricted_networks";
 
     /**
+     * A global rate limit that applies to all networks with NET_CAPABILITY_INTERNET when enabled.
+     *
+     * @hide
+     */
+    public static final String INGRESS_RATE_LIMIT_BYTES_PER_SECOND =
+            "ingress_rate_limit_bytes_per_second";
+
+    /**
      * Get mobile data activity timeout from {@link Settings}.
      *
      * @param context The {@link Context} to query the setting.
@@ -1071,4 +1079,37 @@
         Settings.Global.putString(context.getContentResolver(), UIDS_ALLOWED_ON_RESTRICTED_NETWORKS,
                 uids);
     }
+
+    /**
+     * Get the global network bandwidth rate limit.
+     *
+     * The limit is only applicable to networks that provide internet connectivity. If the setting
+     * is unset, it defaults to -1.
+     *
+     * @param context The {@link Context} to query the setting.
+     * @return The rate limit in number of bytes per second or -1 if disabled.
+     */
+    public static long getIngressRateLimitInBytesPerSecond(@NonNull Context context) {
+        return Settings.Global.getLong(context.getContentResolver(),
+                INGRESS_RATE_LIMIT_BYTES_PER_SECOND, -1);
+    }
+
+    /**
+     * Set the global network bandwidth rate limit.
+     *
+     * The limit is only applicable to networks that provide internet connectivity.
+     *
+     * @param context The {@link Context} to set the setting.
+     * @param rateLimitInBytesPerSec The rate limit in number of bytes per second or -1 to disable.
+     */
+    public static void setIngressRateLimitInBytesPerSecond(@NonNull Context context,
+            @IntRange(from = -1, to = Integer.MAX_VALUE) long rateLimitInBytesPerSec) {
+        if (rateLimitInBytesPerSec < -1) {
+            throw new IllegalArgumentException(
+                    "Rate limit must be within the range [-1, Integer.MAX_VALUE]");
+        }
+        Settings.Global.putLong(context.getContentResolver(),
+                INGRESS_RATE_LIMIT_BYTES_PER_SECOND,
+                rateLimitInBytesPerSec);
+    }
 }
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/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index df4663f..23a3850 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -76,10 +76,15 @@
     LinkProperties getActiveLinkProperties();
     LinkProperties getLinkPropertiesForType(int networkType);
     LinkProperties getLinkProperties(in Network network);
+    LinkProperties redactLinkPropertiesForPackage(in LinkProperties lp, int uid, String packageName,
+            String callingAttributionTag);
 
     NetworkCapabilities getNetworkCapabilities(in Network network, String callingPackageName,
             String callingAttributionTag);
 
+    NetworkCapabilities redactNetworkCapabilitiesForPackage(in NetworkCapabilities nc, int uid,
+            String callingPackageName, String callingAttributionTag);
+
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     NetworkState[] getAllNetworkState();
 
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..1991a58 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
@@ -242,10 +244,31 @@
      * @return whether local traffic is excluded from the VPN network.
      * @hide
      */
-    public boolean getExcludeLocalRouteVpn() {
+    public boolean areLocalRoutesExcludedForVpn() {
         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.
@@ -429,7 +472,7 @@
          */
         @NonNull
         @SystemApi(client = MODULE_LIBRARIES)
-        public Builder setExcludeLocalRoutesVpn(boolean excludeLocalRoutes) {
+        public Builder setLocalRoutesExcludedForVpn(boolean excludeLocalRoutes) {
             mConfig.excludeLocalRouteVpn = excludeLocalRoutes;
             return this;
         }
@@ -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/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 4ae3a06..41be732 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -264,6 +264,7 @@
         mTransportInfo = null;
         mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED;
         mUids = null;
+        mAccessUids.clear();
         mAdministratorUids = new int[0];
         mOwnerUid = Process.INVALID_UID;
         mSSID = null;
@@ -294,6 +295,7 @@
         }
         mSignalStrength = nc.mSignalStrength;
         mUids = (nc.mUids == null) ? null : new ArraySet<>(nc.mUids);
+        setAccessUids(nc.mAccessUids);
         setAdministratorUids(nc.getAdministratorUids());
         mOwnerUid = nc.mOwnerUid;
         mForbiddenNetworkCapabilities = nc.mForbiddenNetworkCapabilities;
@@ -619,22 +621,22 @@
      * Network capabilities that are expected to be mutable, i.e., can change while a particular
      * network is connected.
      */
-    private static final long MUTABLE_CAPABILITIES =
+    private static final long MUTABLE_CAPABILITIES = NetworkCapabilitiesUtils.packBitList(
             // TRUSTED can change when user explicitly connects to an untrusted network in Settings.
             // http://b/18206275
-            (1 << NET_CAPABILITY_TRUSTED)
-            | (1 << NET_CAPABILITY_VALIDATED)
-            | (1 << NET_CAPABILITY_CAPTIVE_PORTAL)
-            | (1 << NET_CAPABILITY_NOT_ROAMING)
-            | (1 << NET_CAPABILITY_FOREGROUND)
-            | (1 << NET_CAPABILITY_NOT_CONGESTED)
-            | (1 << NET_CAPABILITY_NOT_SUSPENDED)
-            | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY)
-            | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED)
-            | (1 << NET_CAPABILITY_NOT_VCN_MANAGED)
+            NET_CAPABILITY_TRUSTED,
+            NET_CAPABILITY_VALIDATED,
+            NET_CAPABILITY_CAPTIVE_PORTAL,
+            NET_CAPABILITY_NOT_ROAMING,
+            NET_CAPABILITY_FOREGROUND,
+            NET_CAPABILITY_NOT_CONGESTED,
+            NET_CAPABILITY_NOT_SUSPENDED,
+            NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+            NET_CAPABILITY_TEMPORARILY_NOT_METERED,
+            NET_CAPABILITY_NOT_VCN_MANAGED,
             // The value of NET_CAPABILITY_HEAD_UNIT is 32, which cannot use int to do bit shift,
             // otherwise there will be an overflow. Use long to do bit shift instead.
-            | (1L << NET_CAPABILITY_HEAD_UNIT);
+            NET_CAPABILITY_HEAD_UNIT);
 
     /**
      * Network capabilities that are not allowed in NetworkRequests. This exists because the
@@ -648,25 +650,26 @@
     // in an infinite loop about these.
     private static final long NON_REQUESTABLE_CAPABILITIES =
             MUTABLE_CAPABILITIES
-            & ~(1 << NET_CAPABILITY_TRUSTED)
-            & ~(1 << NET_CAPABILITY_NOT_VCN_MANAGED);
+            & ~(1L << NET_CAPABILITY_TRUSTED)
+            & ~(1L << NET_CAPABILITY_NOT_VCN_MANAGED);
 
     /**
      * Capabilities that are set by default when the object is constructed.
      */
-    private static final long DEFAULT_CAPABILITIES =
-            (1 << NET_CAPABILITY_NOT_RESTRICTED)
-            | (1 << NET_CAPABILITY_TRUSTED)
-            | (1 << NET_CAPABILITY_NOT_VPN);
+    private static final long DEFAULT_CAPABILITIES = NetworkCapabilitiesUtils.packBitList(
+            NET_CAPABILITY_NOT_RESTRICTED,
+            NET_CAPABILITY_TRUSTED,
+            NET_CAPABILITY_NOT_VPN);
 
     /**
      * Capabilities that are managed by ConnectivityService.
      */
     private static final long CONNECTIVITY_MANAGED_CAPABILITIES =
-            (1 << NET_CAPABILITY_VALIDATED)
-            | (1 << NET_CAPABILITY_CAPTIVE_PORTAL)
-            | (1 << NET_CAPABILITY_FOREGROUND)
-            | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY);
+            NetworkCapabilitiesUtils.packBitList(
+                    NET_CAPABILITY_VALIDATED,
+                    NET_CAPABILITY_CAPTIVE_PORTAL,
+                    NET_CAPABILITY_FOREGROUND,
+                    NET_CAPABILITY_PARTIAL_CONNECTIVITY);
 
     /**
      * Capabilities that are allowed for test networks. This list must be set so that it is safe
@@ -675,14 +678,15 @@
      * INTERNET, IMS, SUPL, etc.
      */
     private static final long TEST_NETWORKS_ALLOWED_CAPABILITIES =
-            (1 << NET_CAPABILITY_NOT_METERED)
-            | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED)
-            | (1 << NET_CAPABILITY_NOT_RESTRICTED)
-            | (1 << NET_CAPABILITY_NOT_VPN)
-            | (1 << NET_CAPABILITY_NOT_ROAMING)
-            | (1 << NET_CAPABILITY_NOT_CONGESTED)
-            | (1 << NET_CAPABILITY_NOT_SUSPENDED)
-            | (1 << NET_CAPABILITY_NOT_VCN_MANAGED);
+            NetworkCapabilitiesUtils.packBitList(
+            NET_CAPABILITY_NOT_METERED,
+            NET_CAPABILITY_TEMPORARILY_NOT_METERED,
+            NET_CAPABILITY_NOT_RESTRICTED,
+            NET_CAPABILITY_NOT_VPN,
+            NET_CAPABILITY_NOT_ROAMING,
+            NET_CAPABILITY_NOT_CONGESTED,
+            NET_CAPABILITY_NOT_SUSPENDED,
+            NET_CAPABILITY_NOT_VCN_MANAGED);
 
     /**
      * Adds the given capability to this {@code NetworkCapability} instance.
@@ -1025,6 +1029,7 @@
         final int[] originalAdministratorUids = getAdministratorUids();
         final TransportInfo originalTransportInfo = getTransportInfo();
         final Set<Integer> originalSubIds = getSubscriptionIds();
+        final Set<Integer> originalAccessUids = new ArraySet<>(mAccessUids);
         clearAll();
         if (0 != (originalCapabilities & (1 << NET_CAPABILITY_NOT_RESTRICTED))) {
             // If the test network is not restricted, then it is only allowed to declare some
@@ -1044,6 +1049,7 @@
         mNetworkSpecifier = originalSpecifier;
         mSignalStrength = originalSignalStrength;
         mTransportInfo = originalTransportInfo;
+        mAccessUids.addAll(originalAccessUids);
 
         // Only retain the owner and administrator UIDs if they match the app registering the remote
         // caller that registered the network.
@@ -1152,12 +1158,13 @@
     /**
      * Allowed transports on an unrestricted test network (in addition to TRANSPORT_TEST).
      */
-    private static final int UNRESTRICTED_TEST_NETWORKS_ALLOWED_TRANSPORTS =
-            1 << TRANSPORT_TEST
-            // Test ethernet networks can be created with EthernetManager#setIncludeTestInterfaces
-            | 1 << TRANSPORT_ETHERNET
-            // Test VPN networks can be created but their UID ranges must be empty.
-            | 1 << TRANSPORT_VPN;
+    private static final long UNRESTRICTED_TEST_NETWORKS_ALLOWED_TRANSPORTS =
+            NetworkCapabilitiesUtils.packBitList(
+                    TRANSPORT_TEST,
+                    // Test eth networks are created with EthernetManager#setIncludeTestInterfaces
+                    TRANSPORT_ETHERNET,
+                    // Test VPN networks can be created but their UID ranges must be empty.
+                    TRANSPORT_VPN);
 
     /**
      * Adds the given transport type to this {@code NetworkCapability} instance.
@@ -1529,9 +1536,12 @@
      */
     public @NonNull NetworkCapabilities setNetworkSpecifier(
             @NonNull NetworkSpecifier networkSpecifier) {
-        if (networkSpecifier != null && Long.bitCount(mTransportTypes) != 1) {
-            throw new IllegalStateException("Must have a single transport specified to use " +
-                    "setNetworkSpecifier");
+        if (networkSpecifier != null
+                // Transport can be test, or test + a single other transport
+                && mTransportTypes != (1L << TRANSPORT_TEST)
+                && Long.bitCount(mTransportTypes & ~(1L << TRANSPORT_TEST)) != 1) {
+            throw new IllegalStateException("Must have a single non-test transport specified to "
+                    + "use setNetworkSpecifier");
         }
 
         mNetworkSpecifier = networkSpecifier;
@@ -1608,8 +1618,8 @@
      * <p>
      * Note that when used to register a network callback, this specifies the minimum acceptable
      * signal strength. When received as the state of an existing network it specifies the current
-     * value. A value of code SIGNAL_STRENGTH_UNSPECIFIED} means no value when received and has no
-     * effect when requesting a callback.
+     * value. A value of {@link #SIGNAL_STRENGTH_UNSPECIFIED} means no value when received and has
+     * no effect when requesting a callback.
      *
      * @param signalStrength the bearer-specific signal strength.
      * @hide
@@ -1809,6 +1819,86 @@
     }
 
     /**
+     * List of UIDs that can always access this network.
+     * <p>
+     * UIDs in this list have access to this network, even if the network doesn't have the
+     * {@link #NET_CAPABILITY_NOT_RESTRICTED} capability and the UID does not hold the
+     * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission.
+     * This is only useful for restricted networks. For non-restricted networks it has no effect.
+     * <p>
+     * This is disallowed in {@link NetworkRequest}, and can only be set by network agents. Network
+     * agents also have restrictions on how they can set these ; they can only back a public
+     * Android API. As such, Ethernet agents can set this when backing the per-UID access API, and
+     * Telephony can set exactly one UID which has to match the manager app for the associated
+     * subscription. Failure to comply with these rules will see this member cleared.
+     * <p>
+     * This member is never null, but can be empty.
+     * @hide
+     */
+    @NonNull
+    private final ArraySet<Integer> mAccessUids = new ArraySet<>();
+
+    /**
+     * Set the list of UIDs that can always access this network.
+     * @param uids
+     * @hide
+     */
+    public void setAccessUids(@NonNull final Set<Integer> uids) {
+        // could happen with nc.set(nc), cheaper than always making a defensive copy
+        if (uids == mAccessUids) return;
+
+        Objects.requireNonNull(uids);
+        mAccessUids.clear();
+        mAccessUids.addAll(uids);
+    }
+
+    /**
+     * The list of UIDs that can always access this network.
+     *
+     * The UIDs in this list can always access this network, even if it is restricted and
+     * the UID doesn't hold the USE_RESTRICTED_NETWORKS permission. This is defined by the
+     * network agent in charge of creating the network.
+     *
+     * The UIDs are only visible to network factories and the system server, since the system
+     * server makes sure to redact them before sending a NetworkCapabilities to a process
+     * that doesn't hold the permission.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public @NonNull Set<Integer> getAccessUids() {
+        return new ArraySet<>(mAccessUids);
+    }
+
+    /** @hide */
+    // For internal clients that know what they are doing and need to avoid the performance hit
+    // of the defensive copy.
+    public @NonNull ArraySet<Integer> getAccessUidsNoCopy() {
+        return mAccessUids;
+    }
+
+    /**
+     * Test whether this UID has special permission to access this network, as per mAccessUids.
+     * @hide
+     */
+    public boolean isAccessUid(int uid) {
+        return mAccessUids.contains(uid);
+    }
+
+    /**
+     * @return whether any UID is in the list of access UIDs
+     * @hide
+     */
+    public boolean hasAccessUids() {
+        return !mAccessUids.isEmpty();
+    }
+
+    private boolean equalsAccessUids(@NonNull NetworkCapabilities other) {
+        return mAccessUids.equals(other.mAccessUids);
+    }
+
+    /**
      * The SSID of the network, or null if not applicable or unknown.
      * <p>
      * This is filled in by wifi code.
@@ -1962,6 +2052,7 @@
                 && equalsSpecifier(that)
                 && equalsTransportInfo(that)
                 && equalsUids(that)
+                && equalsAccessUids(that)
                 && equalsSSID(that)
                 && equalsOwnerUid(that)
                 && equalsPrivateDnsBroken(that)
@@ -1986,15 +2077,16 @@
                 + mSignalStrength * 29
                 + mOwnerUid * 31
                 + Objects.hashCode(mUids) * 37
-                + Objects.hashCode(mSSID) * 41
-                + Objects.hashCode(mTransportInfo) * 43
-                + Objects.hashCode(mPrivateDnsBroken) * 47
-                + Objects.hashCode(mRequestorUid) * 53
-                + Objects.hashCode(mRequestorPackageName) * 59
-                + Arrays.hashCode(mAdministratorUids) * 61
-                + Objects.hashCode(mSubIds) * 67
-                + Objects.hashCode(mUnderlyingNetworks) * 71
-                + mEnterpriseId * 73;
+                + Objects.hashCode(mAccessUids) * 41
+                + Objects.hashCode(mSSID) * 43
+                + Objects.hashCode(mTransportInfo) * 47
+                + Objects.hashCode(mPrivateDnsBroken) * 53
+                + Objects.hashCode(mRequestorUid) * 59
+                + Objects.hashCode(mRequestorPackageName) * 61
+                + Arrays.hashCode(mAdministratorUids) * 67
+                + Objects.hashCode(mSubIds) * 71
+                + Objects.hashCode(mUnderlyingNetworks) * 73
+                + mEnterpriseId * 79;
     }
 
     @Override
@@ -2022,6 +2114,7 @@
         dest.writeParcelable((Parcelable) mTransportInfo, flags);
         dest.writeInt(mSignalStrength);
         writeParcelableArraySet(dest, mUids, flags);
+        dest.writeIntArray(CollectionUtils.toIntArray(mAccessUids));
         dest.writeString(mSSID);
         dest.writeBoolean(mPrivateDnsBroken);
         dest.writeIntArray(getAdministratorUids());
@@ -2034,7 +2127,7 @@
     }
 
     public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR =
-        new Creator<NetworkCapabilities>() {
+            new Creator<>() {
             @Override
             public NetworkCapabilities createFromParcel(Parcel in) {
                 NetworkCapabilities netCap = new NetworkCapabilities();
@@ -2048,6 +2141,11 @@
                 netCap.mTransportInfo = in.readParcelable(null);
                 netCap.mSignalStrength = in.readInt();
                 netCap.mUids = readParcelableArraySet(in, null /* ClassLoader, null for default */);
+                final int[] accessUids = in.createIntArray();
+                netCap.mAccessUids.ensureCapacity(accessUids.length);
+                for (int uid : accessUids) {
+                    netCap.mAccessUids.add(uid);
+                }
                 netCap.mSSID = in.readString();
                 netCap.mPrivateDnsBroken = in.readBoolean();
                 netCap.setAdministratorUids(in.createIntArray());
@@ -2124,6 +2222,11 @@
                 sb.append(" Uids: <").append(mUids).append(">");
             }
         }
+
+        if (hasAccessUids()) {
+            sb.append(" AccessUids: <").append(mAccessUids).append(">");
+        }
+
         if (mOwnerUid != Process.INVALID_UID) {
             sb.append(" OwnerUid: ").append(mOwnerUid);
         }
@@ -2300,7 +2403,7 @@
 
     private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) {
         if (!isValidCapability(capability)) {
-            throw new IllegalArgumentException("NetworkCapability " + capability + "out of range");
+            throw new IllegalArgumentException("NetworkCapability " + capability + " out of range");
         }
     }
 
@@ -2909,6 +3012,44 @@
         }
 
         /**
+         * Set a list of UIDs that can always access this network
+         * <p>
+         * Provide a list of UIDs that can access this network even if the network doesn't have the
+         * {@link #NET_CAPABILITY_NOT_RESTRICTED} capability and the UID does not hold the
+         * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission.
+         * <p>
+         * This is disallowed in {@link NetworkRequest}, and can only be set by
+         * {@link NetworkAgent}s, who hold the
+         * {@link android.Manifest.permission.NETWORK_FACTORY} permission.
+         * Network agents also have restrictions on how they can set these ; they can only back
+         * a public Android API. As such, Ethernet agents can set this when backing the per-UID
+         * access API, and Telephony can set exactly one UID which has to match the manager app for
+         * the associated subscription. Failure to comply with these rules will see this member
+         * cleared.
+         * <p>
+         * These UIDs are only visible to network factories and the system server, since the system
+         * server makes sure to redact them before sending a {@link NetworkCapabilities} instance
+         * to a process that doesn't hold the {@link android.Manifest.permission.NETWORK_FACTORY}
+         * permission.
+         * <p>
+         * This list cannot be null, but it can be empty to mean that no UID without the
+         * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission
+         * gets to access this network.
+         *
+         * @param uids the list of UIDs that can always access this network
+         * @return this builder
+         * @hide
+         */
+        @NonNull
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+        public Builder setAccessUids(@NonNull Set<Integer> uids) {
+            Objects.requireNonNull(uids);
+            mCaps.setAccessUids(uids);
+            return this;
+        }
+
+        /**
          * Set the underlying networks of this network.
          *
          * @param networks The underlying networks of this network.
@@ -2941,4 +3082,4 @@
             return new NetworkCapabilities(mCaps);
         }
     }
-}
+}
\ No newline at end of file
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index b7a6076..4f9d845 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -423,6 +423,7 @@
          *
          * @deprecated Use {@link #setNetworkSpecifier(NetworkSpecifier)} instead.
          */
+        @SuppressLint("NewApi") // TODO: b/193460475 remove once fixed
         @Deprecated
         public Builder setNetworkSpecifier(String networkSpecifier) {
             try {
@@ -439,6 +440,15 @@
                 } else if (mNetworkCapabilities.hasTransport(TRANSPORT_TEST)) {
                     return setNetworkSpecifier(new TestNetworkSpecifier(networkSpecifier));
                 } else {
+                    // TODO: b/193460475 remove comment once fixed
+                    // @SuppressLint("NewApi") is due to EthernetNetworkSpecifier being changed
+                    // from @SystemApi to public. EthernetNetworkSpecifier was introduced in Android
+                    // 12 as @SystemApi(client = MODULE_LIBRARIES) and made public in Android 13.
+                    // b/193460475 means in the above situation the tools will think
+                    // EthernetNetworkSpecifier didn't exist in Android 12, causing the NewApi lint
+                    // to fail. In this case, this is actually safe because this code was
+                    // modularized in Android 12, so it can't run on SDKs before Android 12 and is
+                    // therefore guaranteed to always have this class available to it.
                     return setNetworkSpecifier(new EthernetNetworkSpecifier(networkSpecifier));
                 }
             }
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/nearby/Android.bp b/nearby/Android.bp
new file mode 100644
index 0000000..baa0740
--- /dev/null
+++ b/nearby/Android.bp
@@ -0,0 +1,39 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Empty sources and libraries to avoid merge conflicts with downstream
+// branches
+// TODO: remove once the Nearby sources are available in this branch
+filegroup {
+    name: "framework-nearby-java-sources",
+    srcs: [],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
+
+java_library {
+    name: "service-nearby",
+    srcs: [],
+    sdk_version: "module_current",
+    min_sdk_version: "30",
+    apex_available: ["com.android.tethering"],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
diff --git a/nearby/OWNERS b/nearby/OWNERS
new file mode 100644
index 0000000..980c221
--- /dev/null
+++ b/nearby/OWNERS
@@ -0,0 +1,4 @@
+chunzhang@google.com
+weiwa@google.com
+weiwu@google.com
+xlythe@google.com
diff --git a/netd/Android.bp b/netd/Android.bp
new file mode 100644
index 0000000..5ac02d3
--- /dev/null
+++ b/netd/Android.bp
@@ -0,0 +1,83 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+    name: "libnetd_updatable",
+    version_script: "libnetd_updatable.map.txt",
+    stubs: {
+        versions: [
+            "1",
+        ],
+        symbol_file: "libnetd_updatable.map.txt",
+    },
+    defaults: ["netd_defaults"],
+    header_libs: [
+        "bpf_connectivity_headers",
+        "libcutils_headers",
+    ],
+    srcs: [
+        "BpfHandler.cpp",
+        "NetdUpdatable.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libnetdutils",
+    ],
+    export_include_dirs: ["include"],
+    header_abi_checker: {
+        enabled: true,
+        symbol_file: "libnetd_updatable.map.txt",
+    },
+    sanitize: {
+        cfi: true,
+    },
+    apex_available: ["com.android.tethering"],
+    min_sdk_version: "30",
+}
+
+cc_test {
+    name: "netd_updatable_unit_test",
+    defaults: ["netd_defaults"],
+    test_suites: ["general-tests"],
+    require_root: true,  // required by setrlimitForTest()
+    header_libs: [
+        "bpf_connectivity_headers",
+    ],
+    srcs: [
+        "BpfHandlerTest.cpp",
+    ],
+    static_libs: [
+        "libnetd_updatable",
+    ],
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "liblog",
+        "libnetdutils",
+    ],
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+}
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
new file mode 100644
index 0000000..3cd5e13
--- /dev/null
+++ b/netd/BpfHandler.cpp
@@ -0,0 +1,212 @@
+/**
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "BpfHandler"
+
+#include "BpfHandler.h"
+
+#include <linux/bpf.h>
+
+#include <android-base/unique_fd.h>
+#include <bpf/WaitForProgsLoaded.h>
+#include <log/log.h>
+#include <netdutils/UidConstants.h>
+#include <private/android_filesystem_config.h>
+
+#include "BpfSyscallWrappers.h"
+
+namespace android {
+namespace net {
+
+using base::unique_fd;
+using bpf::NONEXISTENT_COOKIE;
+using bpf::getSocketCookie;
+using bpf::retrieveProgram;
+using netdutils::Status;
+using netdutils::statusFromErrno;
+
+constexpr int PER_UID_STATS_ENTRIES_LIMIT = 500;
+// At most 90% of the stats map may be used by tagged traffic entries. This ensures
+// that 10% of the map is always available to count untagged traffic, one entry per UID.
+// Otherwise, apps would be able to avoid data usage accounting entirely by filling up the
+// map with tagged traffic entries.
+constexpr int TOTAL_UID_STATS_ENTRIES_LIMIT = STATS_MAP_SIZE * 0.9;
+
+static_assert(STATS_MAP_SIZE - TOTAL_UID_STATS_ENTRIES_LIMIT > 100,
+              "The limit for stats map is to high, stats data may be lost due to overflow");
+
+static Status attachProgramToCgroup(const char* programPath, const unique_fd& cgroupFd,
+                                    bpf_attach_type type) {
+    unique_fd cgroupProg(retrieveProgram(programPath));
+    if (cgroupProg == -1) {
+        int ret = errno;
+        ALOGE("Failed to get program from %s: %s", programPath, strerror(ret));
+        return statusFromErrno(ret, "cgroup program get failed");
+    }
+    if (android::bpf::attachProgram(type, cgroupProg, cgroupFd)) {
+        int ret = errno;
+        ALOGE("Program from %s attach failed: %s", programPath, strerror(ret));
+        return statusFromErrno(ret, "program attach failed");
+    }
+    return netdutils::status::ok;
+}
+
+static Status initPrograms(const char* cg2_path) {
+    unique_fd cg_fd(open(cg2_path, O_DIRECTORY | O_RDONLY | O_CLOEXEC));
+    if (cg_fd == -1) {
+        int ret = errno;
+        ALOGE("Failed to open the cgroup directory: %s", strerror(ret));
+        return statusFromErrno(ret, "Open the cgroup directory failed");
+    }
+    RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_EGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_EGRESS));
+    RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_INGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_INGRESS));
+
+    // For the devices that support cgroup socket filter, the socket filter
+    // should be loaded successfully by bpfloader. So we attach the filter to
+    // cgroup if the program is pinned properly.
+    // TODO: delete the if statement once all devices should support cgroup
+    // socket filter (ie. the minimum kernel version required is 4.14).
+    if (!access(CGROUP_SOCKET_PROG_PATH, F_OK)) {
+        RETURN_IF_NOT_OK(
+                attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
+    }
+    return netdutils::status::ok;
+}
+
+BpfHandler::BpfHandler()
+    : mPerUidStatsEntriesLimit(PER_UID_STATS_ENTRIES_LIMIT),
+      mTotalUidStatsEntriesLimit(TOTAL_UID_STATS_ENTRIES_LIMIT) {}
+
+BpfHandler::BpfHandler(uint32_t perUidLimit, uint32_t totalLimit)
+    : mPerUidStatsEntriesLimit(perUidLimit), mTotalUidStatsEntriesLimit(totalLimit) {}
+
+Status BpfHandler::init(const char* cg2_path) {
+    // Make sure BPF programs are loaded before doing anything
+    android::bpf::waitForProgsLoaded();
+    ALOGI("BPF programs are loaded");
+
+    RETURN_IF_NOT_OK(initPrograms(cg2_path));
+    RETURN_IF_NOT_OK(initMaps());
+
+    return netdutils::status::ok;
+}
+
+Status BpfHandler::initMaps() {
+    std::lock_guard guard(mMutex);
+    RETURN_IF_NOT_OK(mCookieTagMap.init(COOKIE_TAG_MAP_PATH));
+    RETURN_IF_NOT_OK(mStatsMapA.init(STATS_MAP_A_PATH));
+    RETURN_IF_NOT_OK(mStatsMapB.init(STATS_MAP_B_PATH));
+    RETURN_IF_NOT_OK(mConfigurationMap.init(CONFIGURATION_MAP_PATH));
+    RETURN_IF_NOT_OK(mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, SELECT_MAP_A,
+                                                  BPF_ANY));
+    RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+
+    return netdutils::status::ok;
+}
+
+bool BpfHandler::hasUpdateDeviceStatsPermission(uid_t uid) {
+    // This implementation is the same logic as method ActivityManager#checkComponentPermission.
+    // It implies that the real uid can never be the same as PER_USER_RANGE.
+    uint32_t appId = uid % PER_USER_RANGE;
+    auto permission = mUidPermissionMap.readValue(appId);
+    if (permission.ok() && (permission.value() & BPF_PERMISSION_UPDATE_DEVICE_STATS)) {
+        return true;
+    }
+    return ((appId == AID_ROOT) || (appId == AID_SYSTEM) || (appId == AID_DNS));
+}
+
+int BpfHandler::tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid) {
+    std::lock_guard guard(mMutex);
+    if (chargeUid != realUid && !hasUpdateDeviceStatsPermission(realUid)) {
+        return -EPERM;
+    }
+
+    uint64_t sock_cookie = getSocketCookie(sockFd);
+    if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
+    UidTagValue newKey = {.uid = (uint32_t)chargeUid, .tag = tag};
+
+    uint32_t totalEntryCount = 0;
+    uint32_t perUidEntryCount = 0;
+    // Now we go through the stats map and count how many entries are associated
+    // with chargeUid. If the uid entry hit the limit for each chargeUid, we block
+    // the request to prevent the map from overflow. It is safe here to iterate
+    // over the map since when mMutex is hold, system server cannot toggle
+    // the live stats map and clean it. So nobody can delete entries from the map.
+    const auto countUidStatsEntries = [chargeUid, &totalEntryCount, &perUidEntryCount](
+                                              const StatsKey& key,
+                                              const BpfMap<StatsKey, StatsValue>&) {
+        if (key.uid == chargeUid) {
+            perUidEntryCount++;
+        }
+        totalEntryCount++;
+        return base::Result<void>();
+    };
+    auto configuration = mConfigurationMap.readValue(CURRENT_STATS_MAP_CONFIGURATION_KEY);
+    if (!configuration.ok()) {
+        ALOGE("Failed to get current configuration: %s, fd: %d",
+              strerror(configuration.error().code()), mConfigurationMap.getMap().get());
+        return -configuration.error().code();
+    }
+    if (configuration.value() != SELECT_MAP_A && configuration.value() != SELECT_MAP_B) {
+        ALOGE("unknown configuration value: %d", configuration.value());
+        return -EINVAL;
+    }
+
+    BpfMap<StatsKey, StatsValue>& currentMap =
+            (configuration.value() == SELECT_MAP_A) ? mStatsMapA : mStatsMapB;
+    base::Result<void> res = currentMap.iterate(countUidStatsEntries);
+    if (!res.ok()) {
+        ALOGE("Failed to count the stats entry in map %d: %s", currentMap.getMap().get(),
+              strerror(res.error().code()));
+        return -res.error().code();
+    }
+
+    if (totalEntryCount > mTotalUidStatsEntriesLimit ||
+        perUidEntryCount > mPerUidStatsEntriesLimit) {
+        ALOGE("Too many stats entries in the map, total count: %u, chargeUid(%u) count: %u,"
+              " blocking tag request to prevent map overflow",
+              totalEntryCount, chargeUid, perUidEntryCount);
+        return -EMFILE;
+    }
+    // Update the tag information of a socket to the cookieUidMap. Use BPF_ANY
+    // flag so it will insert a new entry to the map if that value doesn't exist
+    // yet. And update the tag if there is already a tag stored. Since the eBPF
+    // program in kernel only read this map, and is protected by rcu read lock. It
+    // should be fine to cocurrently update the map while eBPF program is running.
+    res = mCookieTagMap.writeValue(sock_cookie, newKey, BPF_ANY);
+    if (!res.ok()) {
+        ALOGE("Failed to tag the socket: %s, fd: %d", strerror(res.error().code()),
+              mCookieTagMap.getMap().get());
+        return -res.error().code();
+    }
+    return 0;
+}
+
+int BpfHandler::untagSocket(int sockFd) {
+    std::lock_guard guard(mMutex);
+    uint64_t sock_cookie = getSocketCookie(sockFd);
+
+    if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
+    base::Result<void> res = mCookieTagMap.deleteValue(sock_cookie);
+    if (!res.ok()) {
+        ALOGE("Failed to untag socket: %s\n", strerror(res.error().code()));
+        return -res.error().code();
+    }
+    return 0;
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/netd/BpfHandler.h b/netd/BpfHandler.h
new file mode 100644
index 0000000..2ede1c1
--- /dev/null
+++ b/netd/BpfHandler.h
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <mutex>
+
+#include <netdutils/Status.h>
+#include "bpf/BpfMap.h"
+#include "bpf_shared.h"
+
+using android::bpf::BpfMap;
+
+namespace android {
+namespace net {
+
+class BpfHandler {
+  public:
+    BpfHandler();
+    BpfHandler(const BpfHandler&) = delete;
+    BpfHandler& operator=(const BpfHandler&) = delete;
+    netdutils::Status init(const char* cg2_path);
+    /*
+     * Tag the socket with the specified tag and uid. In the qtaguid module, the
+     * first tag request that grab the spinlock of rb_tree can update the tag
+     * information first and other request need to wait until it finish. All the
+     * tag request will be addressed in the order of they obtaining the spinlock.
+     * In the eBPF implementation, the kernel will try to update the eBPF map
+     * entry with the tag request. And the hashmap update process is protected by
+     * the spinlock initialized with the map. So the behavior of two modules
+     * should be the same. No additional lock needed.
+     */
+    int tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid);
+
+    /*
+     * The untag process is similar to tag socket and both old qtaguid module and
+     * new eBPF module have spinlock inside the kernel for concurrent update. No
+     * external lock is required.
+     */
+    int untagSocket(int sockFd);
+
+  private:
+    // For testing
+    BpfHandler(uint32_t perUidLimit, uint32_t totalLimit);
+
+    netdutils::Status initMaps();
+    bool hasUpdateDeviceStatsPermission(uid_t uid);
+
+    BpfMap<uint64_t, UidTagValue> mCookieTagMap;
+    BpfMap<StatsKey, StatsValue> mStatsMapA;
+    BpfMap<StatsKey, StatsValue> mStatsMapB;
+    BpfMap<uint32_t, uint8_t> mConfigurationMap;
+    BpfMap<uint32_t, uint8_t> mUidPermissionMap;
+
+    std::mutex mMutex;
+
+    // The limit on the number of stats entries a uid can have in the per uid stats map. BpfHandler
+    // will block that specific uid from tagging new sockets after the limit is reached.
+    const uint32_t mPerUidStatsEntriesLimit;
+
+    // The limit on the total number of stats entries in the per uid stats map. BpfHandler will
+    // block all tagging requests after the limit is reached.
+    const uint32_t mTotalUidStatsEntriesLimit;
+
+    // For testing
+    friend class BpfHandlerTest;
+};
+
+}  // namespace net
+}  // namespace android
\ No newline at end of file
diff --git a/netd/BpfHandlerTest.cpp b/netd/BpfHandlerTest.cpp
new file mode 100644
index 0000000..db59c7c
--- /dev/null
+++ b/netd/BpfHandlerTest.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * BpfHandlerTest.cpp - unit tests for BpfHandler.cpp
+ */
+
+#include <sys/socket.h>
+
+#include <gtest/gtest.h>
+
+#include "BpfHandler.h"
+
+using namespace android::bpf;  // NOLINT(google-build-using-namespace): exempted
+
+namespace android {
+namespace net {
+
+using base::Result;
+
+constexpr int TEST_MAP_SIZE = 10;
+constexpr int TEST_COOKIE = 1;
+constexpr uid_t TEST_UID = 10086;
+constexpr uid_t TEST_UID2 = 54321;
+constexpr uint32_t TEST_TAG = 42;
+constexpr uint32_t TEST_COUNTERSET = 1;
+constexpr uint32_t TEST_PER_UID_STATS_ENTRIES_LIMIT = 3;
+constexpr uint32_t TEST_TOTAL_UID_STATS_ENTRIES_LIMIT = 7;
+
+#define ASSERT_VALID(x) ASSERT_TRUE((x).isValid())
+
+class BpfHandlerTest : public ::testing::Test {
+  protected:
+    BpfHandlerTest()
+        : mBh(TEST_PER_UID_STATS_ENTRIES_LIMIT, TEST_TOTAL_UID_STATS_ENTRIES_LIMIT) {}
+    BpfHandler mBh;
+    BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
+    BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
+    BpfMap<uint32_t, uint8_t> mFakeConfigurationMap;
+    BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
+
+    void SetUp() {
+        std::lock_guard guard(mBh.mMutex);
+        ASSERT_EQ(0, setrlimitForTest());
+
+        mFakeCookieTagMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint64_t), sizeof(UidTagValue),
+                                          TEST_MAP_SIZE, 0));
+        ASSERT_VALID(mFakeCookieTagMap);
+
+        mFakeStatsMapA.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(StatsKey), sizeof(StatsValue),
+                                       TEST_MAP_SIZE, 0));
+        ASSERT_VALID(mFakeStatsMapA);
+
+        mFakeConfigurationMap.reset(
+                createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), 1, 0));
+        ASSERT_VALID(mFakeConfigurationMap);
+
+        mFakeUidPermissionMap.reset(
+                createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
+        ASSERT_VALID(mFakeUidPermissionMap);
+
+        mBh.mCookieTagMap.reset(dupFd(mFakeCookieTagMap.getMap()));
+        ASSERT_VALID(mBh.mCookieTagMap);
+        mBh.mStatsMapA.reset(dupFd(mFakeStatsMapA.getMap()));
+        ASSERT_VALID(mBh.mStatsMapA);
+        mBh.mConfigurationMap.reset(dupFd(mFakeConfigurationMap.getMap()));
+        ASSERT_VALID(mBh.mConfigurationMap);
+        // Always write to stats map A by default.
+        ASSERT_RESULT_OK(mBh.mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY,
+                                                          SELECT_MAP_A, BPF_ANY));
+        mBh.mUidPermissionMap.reset(dupFd(mFakeUidPermissionMap.getMap()));
+        ASSERT_VALID(mBh.mUidPermissionMap);
+    }
+
+    int dupFd(const android::base::unique_fd& mapFd) {
+        return fcntl(mapFd.get(), F_DUPFD_CLOEXEC, 0);
+    }
+
+    int setUpSocketAndTag(int protocol, uint64_t* cookie, uint32_t tag, uid_t uid,
+                          uid_t realUid) {
+        int sock = socket(protocol, SOCK_STREAM | SOCK_CLOEXEC, 0);
+        EXPECT_LE(0, sock);
+        *cookie = getSocketCookie(sock);
+        EXPECT_NE(NONEXISTENT_COOKIE, *cookie);
+        EXPECT_EQ(0, mBh.tagSocket(sock, tag, uid, realUid));
+        return sock;
+    }
+
+    void expectUidTag(uint64_t cookie, uid_t uid, uint32_t tag) {
+        Result<UidTagValue> tagResult = mFakeCookieTagMap.readValue(cookie);
+        ASSERT_RESULT_OK(tagResult);
+        EXPECT_EQ(uid, tagResult.value().uid);
+        EXPECT_EQ(tag, tagResult.value().tag);
+    }
+
+    void expectNoTag(uint64_t cookie) { EXPECT_FALSE(mFakeCookieTagMap.readValue(cookie).ok()); }
+
+    void populateFakeStats(uint64_t cookie, uint32_t uid, uint32_t tag, StatsKey* key) {
+        UidTagValue cookieMapkey = {.uid = (uint32_t)uid, .tag = tag};
+        EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, cookieMapkey, BPF_ANY));
+        *key = {.uid = uid, .tag = tag, .counterSet = TEST_COUNTERSET, .ifaceIndex = 1};
+        StatsValue statsMapValue = {.rxPackets = 1, .rxBytes = 100};
+        EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
+        key->tag = 0;
+        EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
+        // put tag information back to statsKey
+        key->tag = tag;
+    }
+
+    template <class Key, class Value>
+    void expectMapEmpty(BpfMap<Key, Value>& map) {
+        auto isEmpty = map.isEmpty();
+        EXPECT_RESULT_OK(isEmpty);
+        EXPECT_TRUE(isEmpty.value());
+    }
+
+    void expectTagSocketReachLimit(uint32_t tag, uint32_t uid) {
+        int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+        EXPECT_LE(0, sock);
+        if (sock < 0) return;
+        uint64_t sockCookie = getSocketCookie(sock);
+        EXPECT_NE(NONEXISTENT_COOKIE, sockCookie);
+        EXPECT_EQ(-EMFILE, mBh.tagSocket(sock, tag, uid, uid));
+        expectNoTag(sockCookie);
+
+        // Delete stats entries then tag socket success
+        StatsKey key = {.uid = uid, .tag = 0, .counterSet = TEST_COUNTERSET, .ifaceIndex = 1};
+        ASSERT_RESULT_OK(mFakeStatsMapA.deleteValue(key));
+        EXPECT_EQ(0, mBh.tagSocket(sock, tag, uid, uid));
+        expectUidTag(sockCookie, uid, tag);
+    }
+};
+
+TEST_F(BpfHandlerTest, TestTagSocketV4) {
+    uint64_t sockCookie;
+    int v4socket = setUpSocketAndTag(AF_INET, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
+    expectUidTag(sockCookie, TEST_UID, TEST_TAG);
+    ASSERT_EQ(0, mBh.untagSocket(v4socket));
+    expectNoTag(sockCookie);
+    expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestReTagSocket) {
+    uint64_t sockCookie;
+    int v4socket = setUpSocketAndTag(AF_INET, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
+    expectUidTag(sockCookie, TEST_UID, TEST_TAG);
+    ASSERT_EQ(0, mBh.tagSocket(v4socket, TEST_TAG + 1, TEST_UID + 1, TEST_UID + 1));
+    expectUidTag(sockCookie, TEST_UID + 1, TEST_TAG + 1);
+}
+
+TEST_F(BpfHandlerTest, TestTagTwoSockets) {
+    uint64_t sockCookie1;
+    uint64_t sockCookie2;
+    int v4socket1 = setUpSocketAndTag(AF_INET, &sockCookie1, TEST_TAG, TEST_UID, TEST_UID);
+    setUpSocketAndTag(AF_INET, &sockCookie2, TEST_TAG, TEST_UID, TEST_UID);
+    expectUidTag(sockCookie1, TEST_UID, TEST_TAG);
+    expectUidTag(sockCookie2, TEST_UID, TEST_TAG);
+    ASSERT_EQ(0, mBh.untagSocket(v4socket1));
+    expectNoTag(sockCookie1);
+    expectUidTag(sockCookie2, TEST_UID, TEST_TAG);
+    ASSERT_FALSE(mFakeCookieTagMap.getNextKey(sockCookie2).ok());
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketV6) {
+    uint64_t sockCookie;
+    int v6socket = setUpSocketAndTag(AF_INET6, &sockCookie, TEST_TAG, TEST_UID, TEST_UID);
+    expectUidTag(sockCookie, TEST_UID, TEST_TAG);
+    ASSERT_EQ(0, mBh.untagSocket(v6socket));
+    expectNoTag(sockCookie);
+    expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestTagInvalidSocket) {
+    int invalidSocket = -1;
+    ASSERT_GT(0, mBh.tagSocket(invalidSocket, TEST_TAG, TEST_UID, TEST_UID));
+    expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketWithoutPermission) {
+    int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+    ASSERT_NE(-1, sock);
+    ASSERT_EQ(-EPERM, mBh.tagSocket(sock, TEST_TAG, TEST_UID, TEST_UID2));
+    expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketWithPermission) {
+    // Grant permission to real uid. In practice, the uid permission map will be updated by
+    // TrafficController::setPermissionForUids().
+    uid_t realUid = TEST_UID2;
+    ASSERT_RESULT_OK(mFakeUidPermissionMap.writeValue(realUid,
+                     BPF_PERMISSION_UPDATE_DEVICE_STATS, BPF_ANY));
+
+    // Tag a socket to a different uid other then realUid.
+    uint64_t sockCookie;
+    int v6socket = setUpSocketAndTag(AF_INET6, &sockCookie, TEST_TAG, TEST_UID, realUid);
+    expectUidTag(sockCookie, TEST_UID, TEST_TAG);
+    EXPECT_EQ(0, mBh.untagSocket(v6socket));
+    expectNoTag(sockCookie);
+    expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestUntagInvalidSocket) {
+    int invalidSocket = -1;
+    ASSERT_GT(0, mBh.untagSocket(invalidSocket));
+    int v4socket = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+    ASSERT_GT(0, mBh.untagSocket(v4socket));
+    expectMapEmpty(mFakeCookieTagMap);
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketReachLimitFail) {
+    uid_t uid = TEST_UID;
+    StatsKey tagStatsMapKey[3];
+    for (int i = 0; i < 3; i++) {
+        uint64_t cookie = TEST_COOKIE + i;
+        uint32_t tag = TEST_TAG + i;
+        populateFakeStats(cookie, uid, tag, &tagStatsMapKey[i]);
+    }
+    expectTagSocketReachLimit(TEST_TAG, TEST_UID);
+}
+
+TEST_F(BpfHandlerTest, TestTagSocketReachTotalLimitFail) {
+    StatsKey tagStatsMapKey[4];
+    for (int i = 0; i < 4; i++) {
+        uint64_t cookie = TEST_COOKIE + i;
+        uint32_t tag = TEST_TAG + i;
+        uid_t uid = TEST_UID + i;
+        populateFakeStats(cookie, uid, tag, &tagStatsMapKey[i]);
+    }
+    expectTagSocketReachLimit(TEST_TAG, TEST_UID);
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/netd/NetdUpdatable.cpp b/netd/NetdUpdatable.cpp
new file mode 100644
index 0000000..f0997fc
--- /dev/null
+++ b/netd/NetdUpdatable.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "NetdUpdatable"
+
+#include "NetdUpdatable.h"
+
+#include <android-base/logging.h>
+#include <netdutils/Status.h>
+
+#include "NetdUpdatablePublic.h"
+
+int libnetd_updatable_init(const char* cg2_path) {
+    android::base::InitLogging(/*argv=*/nullptr);
+    LOG(INFO) << __func__ << ": Initializing";
+
+    android::net::gNetdUpdatable = android::net::NetdUpdatable::getInstance();
+    android::netdutils::Status ret = android::net::gNetdUpdatable->mBpfHandler.init(cg2_path);
+    if (!android::netdutils::isOk(ret)) {
+        LOG(ERROR) << __func__ << ": BPF handler init failed";
+        return -ret.code();
+    }
+    return 0;
+}
+
+int libnetd_updatable_tagSocket(int sockFd, uint32_t tag, uid_t chargeUid, uid_t realUid) {
+    if (android::net::gNetdUpdatable == nullptr) return -EPERM;
+    return android::net::gNetdUpdatable->mBpfHandler.tagSocket(sockFd, tag, chargeUid, realUid);
+}
+
+int libnetd_updatable_untagSocket(int sockFd) {
+    if (android::net::gNetdUpdatable == nullptr) return -EPERM;
+    return android::net::gNetdUpdatable->mBpfHandler.untagSocket(sockFd);
+}
+
+namespace android {
+namespace net {
+
+NetdUpdatable* gNetdUpdatable = nullptr;
+
+NetdUpdatable* NetdUpdatable::getInstance() {
+    // Instantiated on first use.
+    static NetdUpdatable instance;
+    return &instance;
+}
+
+}  // namespace net
+}  // namespace android
diff --git a/netd/NetdUpdatable.h b/netd/NetdUpdatable.h
new file mode 100644
index 0000000..333037f
--- /dev/null
+++ b/netd/NetdUpdatable.h
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "BpfHandler.h"
+
+namespace android {
+namespace net {
+
+class NetdUpdatable {
+  public:
+    NetdUpdatable() = default;
+    NetdUpdatable(const NetdUpdatable&) = delete;
+    NetdUpdatable& operator=(const NetdUpdatable&) = delete;
+    static NetdUpdatable* getInstance();
+
+    BpfHandler mBpfHandler;
+};
+
+extern NetdUpdatable* gNetdUpdatable;
+
+}  // namespace net
+}  // namespace android
\ No newline at end of file
diff --git a/netd/include/NetdUpdatablePublic.h b/netd/include/NetdUpdatablePublic.h
new file mode 100644
index 0000000..1ca5ea2
--- /dev/null
+++ b/netd/include/NetdUpdatablePublic.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+/*
+ * Initial function for libnetd_updatable library.
+ *
+ * The function uses |cg2_path| as cgroup v2 mount location to attach BPF programs so that the
+ * kernel can record packet number, size, etc. in BPF maps when packets pass through, and let user
+ * space retrieve statistics.
+ *
+ * Returns 0 on success, or a negative POSIX error code (see errno.h) on
+ * failure.
+ */
+int libnetd_updatable_init(const char* cg2_path);
+
+/*
+ * Set the socket tag and owning UID for traffic statistics on the specified socket. Permission
+ * check is performed based on the |realUid| before socket tagging.
+ *
+ * The |sockFd| is a file descriptor of the socket that needs to tag. The |tag| is the mark to tag.
+ * It can be an arbitrary value in uint32_t range. The |chargeUid| is owning uid which will be
+ * tagged along with the |tag|. The |realUid| is an effective uid of the calling process, which is
+ * used for permission check before socket tagging.
+ *
+ * Returns 0 on success, or a negative POSIX error code (see errno.h) on failure.
+ */
+int libnetd_updatable_tagSocket(int sockFd, uint32_t tag, uid_t chargeUid,
+                                                       uid_t realUid);
+
+/*
+ * Untag a network socket. Future traffic on this socket will no longer be associated with any
+ * previously configured tag and uid.
+ *
+ * The |sockFd| is a file descriptor of the socket that wants to untag.
+ *
+ * Returns 0 on success, or a negative POSIX error code (see errno.h) on failure.
+ */
+int libnetd_updatable_untagSocket(int sockFd);
+
+__END_DECLS
\ No newline at end of file
diff --git a/netd/libnetd_updatable.map.txt b/netd/libnetd_updatable.map.txt
new file mode 100644
index 0000000..dcb11a1
--- /dev/null
+++ b/netd/libnetd_updatable.map.txt
@@ -0,0 +1,27 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# This lists the entry points visible to applications that use the libnetd_updatable
+# library. Other entry points present in the library won't be usable.
+
+LIBNETD_UPDATABLE {
+  global:
+    libnetd_updatable_init; # apex
+    libnetd_updatable_tagSocket; # apex
+    libnetd_updatable_untagSocket; # apex
+  local:
+    *;
+};
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 48c74c6..8ba0768 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -36,21 +36,26 @@
     ],
     libs: [
         "framework-annotations-lib",
-        "framework-connectivity.impl",
-        "framework-connectivity-tiramisu.impl",
+        "framework-connectivity-pre-jarjar",
+        "framework-connectivity-tiramisu-pre-jarjar",
+        "framework-tethering.stubs.module_lib",
         "service-connectivity-pre-jarjar",
         "unsupportedappusage",
     ],
     static_libs: [
-        "modules-utils-build",
+        // Do not add static_libs here if they are already included in framework-connectivity
+        // or in service-connectivity. They are not necessary (included via
+        // service-connectivity-pre-jarjar), and in the case of code that is already in
+        // framework-connectivity, the classes would be included in the apex twice.
         "modules-utils-statemachine",
-        "net-utils-framework-common",
     ],
     apex_available: [
         "com.android.tethering",
     ],
     visibility: [
+        "//frameworks/base/tests/vcn",
         "//packages/modules/Connectivity/service",
         "//packages/modules/Connectivity/tests:__subpackages__",
+        "//packages/modules/IPsec/tests/iketests",
     ],
 }
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-t/src/com/android/server/ConnectivityServiceInitializer.java b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
index 23d8bdc..67757af 100644
--- a/service-t/src/com/android/server/ConnectivityServiceInitializer.java
+++ b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
@@ -28,6 +28,7 @@
 public final class ConnectivityServiceInitializer extends SystemService {
     private static final String TAG = ConnectivityServiceInitializer.class.getSimpleName();
     private final ConnectivityService mConnectivity;
+    private final IpSecService mIpSecService;
     private final NsdService mNsdService;
 
     public ConnectivityServiceInitializer(Context context) {
@@ -35,6 +36,7 @@
         // Load JNI libraries used by ConnectivityService and its dependencies
         System.loadLibrary("service-connectivity");
         mConnectivity = new ConnectivityService(context);
+        mIpSecService = createIpSecService(context);
         mNsdService = createNsdService(context);
     }
 
@@ -43,12 +45,27 @@
         Log.i(TAG, "Registering " + Context.CONNECTIVITY_SERVICE);
         publishBinderService(Context.CONNECTIVITY_SERVICE, mConnectivity,
                 /* allowIsolated= */ false);
+
+        if (mIpSecService != null) {
+            Log.i(TAG, "Registering " + Context.IPSEC_SERVICE);
+            publishBinderService(Context.IPSEC_SERVICE, mIpSecService, /* allowIsolated= */ false);
+        }
+
         if (mNsdService != null) {
             Log.i(TAG, "Registering " + Context.NSD_SERVICE);
             publishBinderService(Context.NSD_SERVICE, mNsdService, /* allowIsolated= */ false);
         }
     }
 
+    /**
+     * Return IpSecService instance, or null if current SDK is lower than T.
+     */
+    private IpSecService createIpSecService(final Context context) {
+        if (!SdkLevel.isAtLeastT()) return null;
+
+        return new IpSecService(context);
+    }
+
     /** Return NsdService instance or null if current SDK is lower than T */
     private NsdService createNsdService(final Context context) {
         if (!SdkLevel.isAtLeastT()) return null;
diff --git a/service/Android.bp b/service/Android.bp
index 33d4b66..33fe3ea 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -20,9 +20,9 @@
 }
 
 // The library name match the service-connectivity jarjar rules that put the JNI utils in the
-// com.android.connectivity.com.android.net.module.util package.
+// android.net.connectivity.com.android.net.module.util package.
 cc_library_shared {
-    name: "libcom_android_connectivity_com_android_net_module_util_jni",
+    name: "libandroid_net_connectivity_com_android_net_module_util_jni",
     min_sdk_version: "30",
     cflags: [
         "-Wall",
@@ -33,10 +33,8 @@
     srcs: [
         "jni/com_android_net_module_util/onload.cpp",
     ],
-    stl: "libc++_static",
     static_libs: [
         "libnet_utils_device_common_bpfjni",
-        "libtcutils",
     ],
     shared_libs: [
         "liblog",
@@ -57,21 +55,24 @@
         "-Wthread-safety",
     ],
     srcs: [
-        "jni/com_android_server_TestNetworkService.cpp",
+        "jni/com_android_server_BpfNetMaps.cpp",
         "jni/com_android_server_connectivity_ClatCoordinator.cpp",
+        "jni/com_android_server_TestNetworkService.cpp",
         "jni/onload.cpp",
     ],
-    stl: "libc++_static",
     header_libs: [
-        "libbase_headers",
+        "bpf_connectivity_headers",
     ],
     static_libs: [
-        "libbase",
         "libclat",
         "libip_checksum",
         "libnetjniutils",
+        "libtraffic_controller",
+        "netd_aidl_interface-lateststable-ndk",
     ],
     shared_libs: [
+        "libbase",
+        "libnetdutils",
         "liblog",
         "libnativehelper",
     ],
@@ -93,20 +94,20 @@
     ],
     libs: [
         "framework-annotations-lib",
-        "framework-connectivity.impl",
+        "framework-connectivity-pre-jarjar",
         "framework-tethering.stubs.module_lib",
         "framework-wifi.stubs.module_lib",
         "unsupportedappusage",
         "ServiceConnectivityResources",
     ],
     static_libs: [
+        // Do not add libs here if they are already included
+        // in framework-connectivity
         "dnsresolver_aidl_interface-V9-java",
-        "modules-utils-build",
         "modules-utils-shell-command-handler",
         "net-utils-device-common",
         "net-utils-device-common-bpf",
         "net-utils-device-common-netlink",
-        "net-utils-framework-common",
         "netd-client",
         "networkstack-client",
         "PlatformProperties",
@@ -152,8 +153,9 @@
     static_libs: [
         "service-connectivity-pre-jarjar",
         "service-connectivity-tiramisu-pre-jarjar",
+        "service-nearby",
     ],
-    jarjar_rules: "jarjar-rules.txt",
+    jarjar_rules: ":connectivity-jarjar-rules",
     apex_available: [
         "com.android.tethering",
     ],
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index f658a5e..e3b26fd 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -1,6 +1,15 @@
+# Classes in framework-connectivity are restricted to the android.net package.
+# This cannot be changed because it is harcoded in ART in S.
+# Any missing jarjar rule for framework-connectivity would be caught by the
+# build as an unexpected class outside of the android.net package.
+rule com.android.net.module.util.** android.net.connectivity.@0
+rule com.android.modules.utils.** android.net.connectivity.@0
+rule android.net.NetworkFactory* android.net.connectivity.@0
+
+# From modules-utils-preconditions
+rule com.android.internal.util.Preconditions* android.net.connectivity.@0
+
 rule android.sysprop.** com.android.connectivity.@0
-rule com.android.net.module.util.** com.android.connectivity.@0
-rule com.android.modules.utils.** com.android.connectivity.@0
 
 # internal util classes from framework-connectivity-shared-srcs
 rule android.util.LocalLog* com.android.connectivity.@0
@@ -23,9 +32,6 @@
 rule android.net.ResolverParamsParcel* com.android.connectivity.@0
 # Also includes netd event listener AIDL, but this is handled by netd-client rules
 
-# From net-utils-device-common
-rule android.net.NetworkFactory* com.android.connectivity.@0
-
 # From netd-client (newer AIDLs should go to android.net.netd.aidl)
 rule android.net.netd.aidl.** com.android.connectivity.@0
 # Avoid including android.net.INetdEventCallback, used in tests but not part of the module
diff --git a/service/jni/com_android_net_module_util/onload.cpp b/service/jni/com_android_net_module_util/onload.cpp
index 07ae31c..2f09e55 100644
--- a/service/jni/com_android_net_module_util/onload.cpp
+++ b/service/jni/com_android_net_module_util/onload.cpp
@@ -30,10 +30,10 @@
     }
 
     if (register_com_android_net_module_util_BpfMap(env,
-            "com/android/connectivity/com/android/net/module/util/BpfMap") < 0) return JNI_ERR;
+            "android/net/connectivity/com/android/net/module/util/BpfMap") < 0) return JNI_ERR;
 
     if (register_com_android_net_module_util_TcUtils(env,
-            "com/android/connectivity/com/android/net/module/util/TcUtils") < 0) return JNI_ERR;
+            "android/net/connectivity/com/android/net/module/util/TcUtils") < 0) return JNI_ERR;
 
     return JNI_VERSION_1_6;
 }
diff --git a/service/native/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
similarity index 82%
rename from service/native/jni/com_android_server_BpfNetMaps.cpp
rename to service/jni/com_android_server_BpfNetMaps.cpp
index 7ab4d46..85cfc09 100644
--- a/service/native/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -16,17 +16,17 @@
 
 #define LOG_TAG "TrafficControllerJni"
 
+#include "TrafficController.h"
+
+#include <bpf_shared.h>
 #include <jni.h>
+#include <log/log.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <net/if.h>
 #include <vector>
 
-#include "TrafficController.h"
-#include "android-base/logging.h"
-#include "bpf_shared.h"
-#include "utils/Log.h"
 
 using android::net::TrafficController;
 using android::netdutils::Status;
@@ -41,7 +41,7 @@
 static void native_init(JNIEnv* env, jobject clazz) {
   Status status = mTc.start();
    if (!isOk(status)) {
-    ALOGE("%s failed", __func__);
+    ALOGE("%s failed, error code = %d", __func__, status.code());
   }
 }
 
@@ -50,7 +50,7 @@
   Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
       TrafficController::IptOp::IptOpInsert);
   if (!isOk(status)) {
-    ALOGE("%s failed, errer code = %d", __func__, status.code());
+    ALOGE("%s failed, error code = %d", __func__, status.code());
   }
   return (jint)status.code();
 }
@@ -60,7 +60,7 @@
   Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
       TrafficController::IptOp::IptOpDelete);
   if (!isOk(status)) {
-    ALOGE("%s failed, errer code = %d", __func__, status.code());
+    ALOGE("%s failed, error code = %d", __func__, status.code());
   }
   return (jint)status.code();
 }
@@ -70,7 +70,7 @@
   Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
       TrafficController::IptOp::IptOpInsert);
   if (!isOk(status)) {
-    ALOGE("%s failed, errer code = %d", __func__, status.code());
+    ALOGE("%s failed, error code = %d", __func__, status.code());
   }
   return (jint)status.code();
 }
@@ -80,7 +80,7 @@
   Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
       TrafficController::IptOp::IptOpDelete);
   if (!isOk(status)) {
-    ALOGD("%s failed, errer code = %d", __func__, status.code());
+    ALOGD("%s failed, error code = %d", __func__, status.code());
   }
   return (jint)status.code();
 }
@@ -116,27 +116,11 @@
     return (jint)res;
 }
 
-static FirewallType getFirewallType(ChildChain chain) {
-    switch (chain) {
-        case DOZABLE:
-            return ALLOWLIST;
-        case STANDBY:
-            return DENYLIST;
-        case POWERSAVE:
-            return ALLOWLIST;
-        case RESTRICTED:
-            return ALLOWLIST;
-        case NONE:
-        default:
-            return DENYLIST;
-    }
-}
-
 static jint native_setUidRule(JNIEnv* env, jobject clazz, jint childChain, jint uid,
                           jint firewallRule) {
     auto chain = static_cast<ChildChain>(childChain);
     auto rule = static_cast<FirewallRule>(firewallRule);
-    FirewallType fType = getFirewallType(chain);
+    FirewallType fType = mTc.getFirewallType(chain);
 
     int res = mTc.changeUidOwnerRule(chain, uid, rule, fType);
     if (res) {
@@ -202,24 +186,6 @@
     mTc.setPermissionForUids(permission, data);
 }
 
-static jint native_setCounterSet(JNIEnv* env, jobject clazz, jint setNum, jint uid) {
-    uid_t callingUid = getuid();
-    int res = mTc.setCounterSet(setNum, (uid_t)uid, callingUid);
-    if (res) {
-      ALOGE("%s failed, error code = %d", __func__, res);
-    }
-    return (jint)res;
-}
-
-static jint native_deleteTagData(JNIEnv* env, jobject clazz, jint tagNum, jint uid) {
-    uid_t callingUid = getuid();
-    int res = mTc.deleteTagData(tagNum, (uid_t)uid, callingUid);
-    if (res) {
-      ALOGE("%s failed, error code = %d", __func__, res);
-    }
-    return (jint)res;
-}
-
 /*
  * JNI registration.
  */
@@ -250,10 +216,6 @@
     (void*)native_swapActiveStatsMap},
     {"native_setPermissionForUids", "(I[I)V",
     (void*)native_setPermissionForUids},
-    {"native_setCounterSet", "(II)I",
-    (void*)native_setCounterSet},
-    {"native_deleteTagData", "(II)I",
-    (void*)native_deleteTagData},
 };
 // clang-format on
 
diff --git a/service/jni/com_android_server_TestNetworkService.cpp b/service/jni/com_android_server_TestNetworkService.cpp
index 1a0de32..4efd0e1 100644
--- a/service/jni/com_android_server_TestNetworkService.cpp
+++ b/service/jni/com_android_server_TestNetworkService.cpp
@@ -98,7 +98,7 @@
     {"jniCreateTunTap", "(ZLjava/lang/String;)I", (void*)create},
 };
 
-int register_android_server_TestNetworkService(JNIEnv* env) {
+int register_com_android_server_TestNetworkService(JNIEnv* env) {
     return jniRegisterNativeMethods(env, "com/android/server/TestNetworkService", gMethods,
                                     NELEM(gMethods));
 }
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index b445462..ee512ec 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -519,7 +519,7 @@
          (void*)com_android_server_connectivity_ClatCoordinator_stopClatd},
 };
 
-int register_android_server_connectivity_ClatCoordinator(JNIEnv* env) {
+int register_com_android_server_connectivity_ClatCoordinator(JNIEnv* env) {
     return jniRegisterNativeMethods(env, "com/android/server/connectivity/ClatCoordinator",
                                     gMethods, NELEM(gMethods));
 }
diff --git a/service/jni/onload.cpp b/service/jni/onload.cpp
index 04d9671..facdad7 100644
--- a/service/jni/onload.cpp
+++ b/service/jni/onload.cpp
@@ -19,8 +19,9 @@
 
 namespace android {
 
-int register_android_server_TestNetworkService(JNIEnv* env);
-int register_android_server_connectivity_ClatCoordinator(JNIEnv* env);
+int register_com_android_server_TestNetworkService(JNIEnv* env);
+int register_com_android_server_connectivity_ClatCoordinator(JNIEnv* env);
+int register_com_android_server_BpfNetMaps(JNIEnv* env);
 
 extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
     JNIEnv *env;
@@ -29,11 +30,15 @@
         return JNI_ERR;
     }
 
-    if (register_android_server_TestNetworkService(env) < 0) {
+    if (register_com_android_server_TestNetworkService(env) < 0) {
         return JNI_ERR;
     }
 
-    if (register_android_server_connectivity_ClatCoordinator(env) < 0) {
+    if (register_com_android_server_connectivity_ClatCoordinator(env) < 0) {
+        return JNI_ERR;
+    }
+
+    if (register_com_android_server_BpfNetMaps(env) < 0) {
         return JNI_ERR;
     }
 
diff --git a/service/native/Android.bp b/service/native/Android.bp
index b49457d..cb26bc3 100644
--- a/service/native/Android.bp
+++ b/service/native/Android.bp
@@ -26,11 +26,8 @@
     ],
     header_libs: [
         "bpf_connectivity_headers",
-        "bpf_headers",
-        "bpf_syscall_wrappers",
     ],
     static_libs: [
-        "libnetdutils",
         // TrafficController would use the constants of INetd so that add
         // netd_aidl_interface-lateststable-ndk.
         "netd_aidl_interface-lateststable-ndk",
@@ -39,6 +36,7 @@
         // TODO: Find a good way to remove libbase.
         "libbase",
         "libcutils",
+        "libnetdutils",
         "libutils",
         "liblog",
     ],
@@ -52,37 +50,6 @@
     min_sdk_version: "30",
 }
 
-cc_library_shared {
-    name: "libtraffic_controller_jni",
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-unused-parameter",
-        "-Wthread-safety",
-    ],
-    srcs: [
-        "jni/*.cpp",
-    ],
-    header_libs: [
-        "bpf_connectivity_headers",
-    ],
-    static_libs: [
-        "libnetdutils",
-        "libtraffic_controller",
-        "netd_aidl_interface-lateststable-ndk",
-    ],
-    shared_libs: [
-        "libbase",
-        "liblog",
-        "libutils",
-        "libnativehelper",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-    min_sdk_version: "30",
-}
-
 cc_test {
     name: "traffic_controller_unit_test",
     test_suites: ["general-tests"],
@@ -101,6 +68,7 @@
         "libnetdutils",
         "libtraffic_controller",
         "libutils",
+        "libnetd_updatable",
         "netd_aidl_interface-lateststable-ndk",
     ],
 }
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 97fcc43..1cbfd94 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -54,7 +54,6 @@
 using base::StringPrintf;
 using base::unique_fd;
 using bpf::BpfMap;
-using bpf::OVERFLOW_COUNTERSET;
 using bpf::synchronizeKernelRCU;
 using netdutils::DumpWriter;
 using netdutils::getIfaceList;
@@ -75,6 +74,7 @@
 const char* TrafficController::LOCAL_STANDBY = "fw_standby";
 const char* TrafficController::LOCAL_POWERSAVE = "fw_powersave";
 const char* TrafficController::LOCAL_RESTRICTED = "fw_restricted";
+const char* TrafficController::LOCAL_LOW_POWER_STANDBY = "fw_low_power_standby";
 
 static_assert(BPF_PERMISSION_INTERNET == INetd::PERMISSION_INTERNET,
               "Mismatch between BPF and AIDL permissions: PERMISSION_INTERNET");
@@ -97,6 +97,7 @@
     FLAG_MSG_TRANS(matchType, STANDBY_MATCH, match);
     FLAG_MSG_TRANS(matchType, POWERSAVE_MATCH, match);
     FLAG_MSG_TRANS(matchType, RESTRICTED_MATCH, match);
+    FLAG_MSG_TRANS(matchType, LOW_POWER_STANDBY_MATCH, match);
     FLAG_MSG_TRANS(matchType, IIF_MATCH, match);
     if (match) {
         return StringPrintf("Unknown match: %u", match);
@@ -237,99 +238,6 @@
     return netdutils::status::ok;
 }
 
-int TrafficController::setCounterSet(int counterSetNum, uid_t uid, uid_t callingUid) {
-    if (counterSetNum < 0 || counterSetNum >= OVERFLOW_COUNTERSET) return -EINVAL;
-
-    std::lock_guard guard(mMutex);
-    if (!hasUpdateDeviceStatsPermission(callingUid)) return -EPERM;
-
-    // The default counter set for all uid is 0, so deleting the current counterset for that uid
-    // will automatically set it to 0.
-    if (counterSetNum == 0) {
-        Status res = mUidCounterSetMap.deleteValue(uid);
-        if (isOk(res) || (!isOk(res) && res.code() == ENOENT)) {
-            return 0;
-        } else {
-            ALOGE("Failed to delete the counterSet: %s\n", strerror(res.code()));
-            return -res.code();
-        }
-    }
-    uint8_t tmpCounterSetNum = (uint8_t)counterSetNum;
-    Status res = mUidCounterSetMap.writeValue(uid, tmpCounterSetNum, BPF_ANY);
-    if (!isOk(res)) {
-        ALOGE("Failed to set the counterSet: %s, fd: %d", strerror(res.code()),
-              mUidCounterSetMap.getMap().get());
-        return -res.code();
-    }
-    return 0;
-}
-
-// This method only get called by system_server when an app get uinstalled, it
-// is called inside removeUidsLocked() while holding mStatsLock. So it is safe
-// to iterate and modify the stats maps.
-int TrafficController::deleteTagData(uint32_t tag, uid_t uid, uid_t callingUid) {
-    std::lock_guard guard(mMutex);
-    if (!hasUpdateDeviceStatsPermission(callingUid)) return -EPERM;
-
-    // First we go through the cookieTagMap to delete the target uid tag combination. Or delete all
-    // the tags related to the uid if the tag is 0.
-    const auto deleteMatchedCookieEntries = [uid, tag](const uint64_t& key,
-                                                       const UidTagValue& value,
-                                                       BpfMap<uint64_t, UidTagValue>& map) {
-        if (value.uid == uid && (value.tag == tag || tag == 0)) {
-            auto res = map.deleteValue(key);
-            if (res.ok() || (res.error().code() == ENOENT)) {
-                return base::Result<void>();
-            }
-            ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s\n", key,
-                  strerror(res.error().code()));
-        }
-        // Move forward to next cookie in the map.
-        return base::Result<void>();
-    };
-    mCookieTagMap.iterateWithValue(deleteMatchedCookieEntries);
-    // Now we go through the Tag stats map and delete the data entry with correct uid and tag
-    // combination. Or all tag stats under that uid if the target tag is 0.
-    const auto deleteMatchedUidTagEntries = [uid, tag](const StatsKey& key,
-                                                       BpfMap<StatsKey, StatsValue>& map) {
-        if (key.uid == uid && (key.tag == tag || tag == 0)) {
-            auto res = map.deleteValue(key);
-            if (res.ok() || (res.error().code() == ENOENT)) {
-                //Entry is deleted, use the current key to get a new nextKey;
-                return base::Result<void>();
-            }
-            ALOGE("Failed to delete data(uid=%u, tag=%u): %s\n", key.uid, key.tag,
-                  strerror(res.error().code()));
-        }
-        return base::Result<void>();
-    };
-    mStatsMapB.iterate(deleteMatchedUidTagEntries);
-    mStatsMapA.iterate(deleteMatchedUidTagEntries);
-    // If the tag is not zero, we already deleted all the data entry required. If tag is 0, we also
-    // need to delete the stats stored in uidStatsMap and counterSet map.
-    if (tag != 0) return 0;
-
-    auto res = mUidCounterSetMap.deleteValue(uid);
-    if (!res.ok() && res.error().code() != ENOENT) {
-        ALOGE("Failed to delete counterSet data(uid=%u, tag=%u): %s\n", uid, tag,
-              strerror(res.error().code()));
-    }
-
-    auto deleteAppUidStatsEntry = [uid](const uint32_t& key,
-                                        BpfMap<uint32_t, StatsValue>& map) -> base::Result<void> {
-        if (key == uid) {
-            auto res = map.deleteValue(key);
-            if (res.ok() || (res.error().code() == ENOENT)) {
-                return {};
-            }
-            ALOGE("Failed to delete data(uid=%u): %s", key, strerror(res.error().code()));
-        }
-        return {};
-    };
-    mAppUidStatsMap.iterate(deleteAppUidStatsEntry);
-    return 0;
-}
-
 int TrafficController::addInterface(const char* name, uint32_t ifaceIndex) {
     IfaceValue iface;
     if (ifaceIndex == 0) {
@@ -426,6 +334,8 @@
             return ALLOWLIST;
         case RESTRICTED:
             return ALLOWLIST;
+        case LOW_POWER_STANDBY:
+            return ALLOWLIST;
         case NONE:
         default:
             return DENYLIST;
@@ -448,6 +358,9 @@
         case RESTRICTED:
             res = updateOwnerMapEntry(RESTRICTED_MATCH, uid, rule, type);
             break;
+        case LOW_POWER_STANDBY:
+            res = updateOwnerMapEntry(LOW_POWER_STANDBY_MATCH, uid, rule, type);
+            break;
         case NONE:
         default:
             ALOGW("Unknown child chain: %d", chain);
@@ -526,6 +439,8 @@
         res = replaceRulesInMap(POWERSAVE_MATCH, uids);
     } else if (!name.compare(LOCAL_RESTRICTED)) {
         res = replaceRulesInMap(RESTRICTED_MATCH, uids);
+    } else if (!name.compare(LOCAL_LOW_POWER_STANDBY)) {
+        res = replaceRulesInMap(LOW_POWER_STANDBY_MATCH, uids);
     } else {
         ALOGE("unknown chain name: %s", name.c_str());
         return -EINVAL;
@@ -562,6 +477,9 @@
         case RESTRICTED:
             match = RESTRICTED_MATCH;
             break;
+        case LOW_POWER_STANDBY:
+            match = LOW_POWER_STANDBY_MATCH;
+            break;
         default:
             return -EINVAL;
     }
@@ -683,8 +601,6 @@
     }
 }
 
-const String16 TrafficController::DUMP_KEYWORD = String16("trafficcontroller");
-
 void TrafficController::dump(DumpWriter& dw, bool verbose) {
     std::lock_guard guard(mMutex);
     ScopedIndent indentTop(dw);
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index f401636..9529cae 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -38,6 +38,7 @@
 
 #include "TrafficController.h"
 #include "bpf/BpfUtils.h"
+#include "NetdUpdatablePublic.h"
 
 using namespace android::bpf;  // NOLINT(google-build-using-namespace): grandfathered
 
@@ -54,7 +55,6 @@
 constexpr uid_t TEST_UID3 = 98765;
 constexpr uint32_t TEST_TAG = 42;
 constexpr uint32_t TEST_COUNTERSET = 1;
-constexpr uint32_t DEFAULT_COUNTERSET = 0;
 
 #define ASSERT_VALID(x) ASSERT_TRUE((x).isValid())
 
@@ -63,7 +63,6 @@
     TrafficControllerTest() {}
     TrafficController mTc;
     BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
-    BpfMap<uint32_t, uint8_t> mFakeUidCounterSetMap;
     BpfMap<uint32_t, StatsValue> mFakeAppUidStatsMap;
     BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
     BpfMap<uint32_t, uint8_t> mFakeConfigurationMap;
@@ -78,10 +77,6 @@
                                           TEST_MAP_SIZE, 0));
         ASSERT_VALID(mFakeCookieTagMap);
 
-        mFakeUidCounterSetMap.reset(
-            createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
-        ASSERT_VALID(mFakeUidCounterSetMap);
-
         mFakeAppUidStatsMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(StatsValue),
                                             TEST_MAP_SIZE, 0));
         ASSERT_VALID(mFakeAppUidStatsMap);
@@ -103,8 +98,6 @@
 
         mTc.mCookieTagMap.reset(dupFd(mFakeCookieTagMap.getMap()));
         ASSERT_VALID(mTc.mCookieTagMap);
-        mTc.mUidCounterSetMap.reset(dupFd(mFakeUidCounterSetMap.getMap()));
-        ASSERT_VALID(mTc.mUidCounterSetMap);
         mTc.mAppUidStatsMap.reset(dupFd(mFakeAppUidStatsMap.getMap()));
         ASSERT_VALID(mTc.mAppUidStatsMap);
         mTc.mStatsMapA.reset(dupFd(mFakeStatsMapA.getMap()));
@@ -131,8 +124,6 @@
         EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, cookieMapkey, BPF_ANY));
         *key = {.uid = uid, .tag = tag, .counterSet = TEST_COUNTERSET, .ifaceIndex = 1};
         StatsValue statsMapValue = {.rxPackets = 1, .rxBytes = 100};
-        uint8_t counterSet = TEST_COUNTERSET;
-        EXPECT_RESULT_OK(mFakeUidCounterSetMap.writeValue(uid, counterSet, BPF_ANY));
         EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
         key->tag = 0;
         EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
@@ -258,9 +249,6 @@
         EXPECT_RESULT_OK(cookieMapResult);
         EXPECT_EQ(uid, cookieMapResult.value().uid);
         EXPECT_EQ(tag, cookieMapResult.value().tag);
-        Result<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid);
-        EXPECT_RESULT_OK(counterSetResult);
-        EXPECT_EQ(TEST_COUNTERSET, counterSetResult.value());
         Result<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
         EXPECT_RESULT_OK(statsMapResult);
         EXPECT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
@@ -288,157 +276,6 @@
 
 };
 
-TEST_F(TrafficControllerTest, TestSetCounterSet) {
-    uid_t callingUid = TEST_UID2;
-    addPrivilegedUid(callingUid);
-    ASSERT_EQ(0, mTc.setCounterSet(TEST_COUNTERSET, TEST_UID, callingUid));
-    uid_t uid = TEST_UID;
-    Result<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid);
-    ASSERT_RESULT_OK(counterSetResult);
-    ASSERT_EQ(TEST_COUNTERSET, counterSetResult.value());
-    ASSERT_EQ(0, mTc.setCounterSet(DEFAULT_COUNTERSET, TEST_UID, callingUid));
-    ASSERT_FALSE(mFakeUidCounterSetMap.readValue(uid).ok());
-    expectMapEmpty(mFakeUidCounterSetMap);
-}
-
-TEST_F(TrafficControllerTest, TestSetCounterSetWithoutPermission) {
-    ASSERT_EQ(-EPERM, mTc.setCounterSet(TEST_COUNTERSET, TEST_UID, TEST_UID2));
-    uid_t uid = TEST_UID;
-    ASSERT_FALSE(mFakeUidCounterSetMap.readValue(uid).ok());
-    expectMapEmpty(mFakeUidCounterSetMap);
-}
-
-TEST_F(TrafficControllerTest, TestSetInvalidCounterSet) {
-    uid_t callingUid = TEST_UID2;
-    addPrivilegedUid(callingUid);
-    ASSERT_GT(0, mTc.setCounterSet(OVERFLOW_COUNTERSET, TEST_UID, callingUid));
-    uid_t uid = TEST_UID;
-    ASSERT_FALSE(mFakeUidCounterSetMap.readValue(uid).ok());
-    expectMapEmpty(mFakeUidCounterSetMap);
-}
-
-TEST_F(TrafficControllerTest, TestDeleteTagDataWithoutPermission) {
-    uint64_t cookie = 1;
-    uid_t uid = TEST_UID;
-    uint32_t tag = TEST_TAG;
-    StatsKey tagStatsMapKey;
-    populateFakeStats(cookie, uid, tag, &tagStatsMapKey);
-    ASSERT_EQ(-EPERM, mTc.deleteTagData(0, TEST_UID, TEST_UID2));
-
-    expectFakeStatsUnchanged(cookie, tag, uid, tagStatsMapKey);
-}
-
-TEST_F(TrafficControllerTest, TestDeleteTagData) {
-    uid_t callingUid = TEST_UID2;
-    addPrivilegedUid(callingUid);
-    uint64_t cookie = 1;
-    uid_t uid = TEST_UID;
-    uint32_t tag = TEST_TAG;
-    StatsKey tagStatsMapKey;
-    populateFakeStats(cookie, uid, tag, &tagStatsMapKey);
-    ASSERT_EQ(0, mTc.deleteTagData(TEST_TAG, TEST_UID, callingUid));
-    ASSERT_FALSE(mFakeCookieTagMap.readValue(cookie).ok());
-    Result<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid);
-    ASSERT_RESULT_OK(counterSetResult);
-    ASSERT_EQ(TEST_COUNTERSET, counterSetResult.value());
-    ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey).ok());
-    tagStatsMapKey.tag = 0;
-    Result<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
-    ASSERT_RESULT_OK(statsMapResult);
-    ASSERT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
-    ASSERT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
-    auto appStatsResult = mFakeAppUidStatsMap.readValue(TEST_UID);
-    ASSERT_RESULT_OK(appStatsResult);
-    ASSERT_EQ((uint64_t)1, appStatsResult.value().rxPackets);
-    ASSERT_EQ((uint64_t)100, appStatsResult.value().rxBytes);
-}
-
-TEST_F(TrafficControllerTest, TestDeleteAllUidData) {
-    uid_t callingUid = TEST_UID2;
-    addPrivilegedUid(callingUid);
-    uint64_t cookie = 1;
-    uid_t uid = TEST_UID;
-    uint32_t tag = TEST_TAG;
-    StatsKey tagStatsMapKey;
-    populateFakeStats(cookie, uid, tag, &tagStatsMapKey);
-    ASSERT_EQ(0, mTc.deleteTagData(0, TEST_UID, callingUid));
-    ASSERT_FALSE(mFakeCookieTagMap.readValue(cookie).ok());
-    ASSERT_FALSE(mFakeUidCounterSetMap.readValue(uid).ok());
-    ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey).ok());
-    tagStatsMapKey.tag = 0;
-    ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey).ok());
-    ASSERT_FALSE(mFakeAppUidStatsMap.readValue(TEST_UID).ok());
-}
-
-TEST_F(TrafficControllerTest, TestDeleteDataWithTwoTags) {
-    uid_t callingUid = TEST_UID2;
-    addPrivilegedUid(callingUid);
-    uint64_t cookie1 = 1;
-    uint64_t cookie2 = 2;
-    uid_t uid = TEST_UID;
-    uint32_t tag1 = TEST_TAG;
-    uint32_t tag2 = TEST_TAG + 1;
-    StatsKey tagStatsMapKey1;
-    StatsKey tagStatsMapKey2;
-    populateFakeStats(cookie1, uid, tag1, &tagStatsMapKey1);
-    populateFakeStats(cookie2, uid, tag2, &tagStatsMapKey2);
-    ASSERT_EQ(0, mTc.deleteTagData(TEST_TAG, TEST_UID, callingUid));
-    ASSERT_FALSE(mFakeCookieTagMap.readValue(cookie1).ok());
-    Result<UidTagValue> cookieMapResult = mFakeCookieTagMap.readValue(cookie2);
-    ASSERT_RESULT_OK(cookieMapResult);
-    ASSERT_EQ(TEST_UID, cookieMapResult.value().uid);
-    ASSERT_EQ(TEST_TAG + 1, cookieMapResult.value().tag);
-    Result<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid);
-    ASSERT_RESULT_OK(counterSetResult);
-    ASSERT_EQ(TEST_COUNTERSET, counterSetResult.value());
-    ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey1).ok());
-    Result<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey2);
-    ASSERT_RESULT_OK(statsMapResult);
-    ASSERT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
-    ASSERT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
-}
-
-TEST_F(TrafficControllerTest, TestDeleteDataWithTwoUids) {
-    uid_t callingUid = TEST_UID2;
-    addPrivilegedUid(callingUid);
-    uint64_t cookie1 = 1;
-    uint64_t cookie2 = 2;
-    uid_t uid1 = TEST_UID;
-    uid_t uid2 = TEST_UID + 1;
-    uint32_t tag = TEST_TAG;
-    StatsKey tagStatsMapKey1;
-    StatsKey tagStatsMapKey2;
-    populateFakeStats(cookie1, uid1, tag, &tagStatsMapKey1);
-    populateFakeStats(cookie2, uid2, tag, &tagStatsMapKey2);
-
-    // Delete the stats of one of the uid. Check if it is properly collected by
-    // removedStats.
-    ASSERT_EQ(0, mTc.deleteTagData(0, uid2, callingUid));
-    ASSERT_FALSE(mFakeCookieTagMap.readValue(cookie2).ok());
-    Result<uint8_t> counterSetResult = mFakeUidCounterSetMap.readValue(uid1);
-    ASSERT_RESULT_OK(counterSetResult);
-    ASSERT_EQ(TEST_COUNTERSET, counterSetResult.value());
-    ASSERT_FALSE(mFakeUidCounterSetMap.readValue(uid2).ok());
-    ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey2).ok());
-    tagStatsMapKey2.tag = 0;
-    ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey2).ok());
-    ASSERT_FALSE(mFakeAppUidStatsMap.readValue(uid2).ok());
-    tagStatsMapKey1.tag = 0;
-    Result<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey1);
-    ASSERT_RESULT_OK(statsMapResult);
-    ASSERT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
-    ASSERT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
-    auto appStatsResult = mFakeAppUidStatsMap.readValue(uid1);
-    ASSERT_RESULT_OK(appStatsResult);
-    ASSERT_EQ((uint64_t)1, appStatsResult.value().rxPackets);
-    ASSERT_EQ((uint64_t)100, appStatsResult.value().rxBytes);
-
-    // Delete the stats of the other uid.
-    ASSERT_EQ(0, mTc.deleteTagData(0, uid1, callingUid));
-    ASSERT_FALSE(mFakeStatsMapA.readValue(tagStatsMapKey1).ok());
-    ASSERT_FALSE(mFakeAppUidStatsMap.readValue(uid1).ok());
-}
-
 TEST_F(TrafficControllerTest, TestUpdateOwnerMapEntry) {
     uint32_t uid = TEST_UID;
     ASSERT_TRUE(isOk(mTc.updateOwnerMapEntry(STANDBY_MATCH, uid, DENY, DENYLIST)));
@@ -469,6 +306,7 @@
     checkUidOwnerRuleForChain(STANDBY, STANDBY_MATCH);
     checkUidOwnerRuleForChain(POWERSAVE, POWERSAVE_MATCH);
     checkUidOwnerRuleForChain(RESTRICTED, RESTRICTED_MATCH);
+    checkUidOwnerRuleForChain(LOW_POWER_STANDBY, LOW_POWER_STANDBY_MATCH);
     ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(NONE, TEST_UID, ALLOW, ALLOWLIST));
     ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(INVALID_CHAIN, TEST_UID, ALLOW, ALLOWLIST));
 }
@@ -479,6 +317,7 @@
     checkUidMapReplace("fw_standby", uids, STANDBY_MATCH);
     checkUidMapReplace("fw_powersave", uids, POWERSAVE_MATCH);
     checkUidMapReplace("fw_restricted", uids, RESTRICTED_MATCH);
+    checkUidMapReplace("fw_low_power_standby", uids, LOW_POWER_STANDBY_MATCH);
     ASSERT_EQ(-EINVAL, mTc.replaceUidOwnerMap("unknow", true, uids));
 }
 
@@ -746,5 +585,133 @@
     expectPrivilegedUserSetEmpty();
 }
 
+constexpr uint32_t SOCK_CLOSE_WAIT_US = 30 * 1000;
+constexpr uint32_t ENOBUFS_POLL_WAIT_US = 10 * 1000;
+
+using android::base::Error;
+using android::base::Result;
+using android::bpf::BpfMap;
+
+// This test set up a SkDestroyListener that is running parallel with the production
+// SkDestroyListener. The test will create thousands of sockets and tag them on the
+// production cookieUidTagMap and close them in a short time. When the number of
+// sockets get closed exceeds the buffer size, it will start to return ENOBUFF
+// error. The error will be ignored by the production SkDestroyListener and the
+// test will clean up the tags in tearDown if there is any remains.
+
+// TODO: Instead of test the ENOBUFF error, we can test the production
+// SkDestroyListener to see if it failed to delete a tagged socket when ENOBUFF
+// triggered.
+class NetlinkListenerTest : public testing::Test {
+  protected:
+    NetlinkListenerTest() {}
+    BpfMap<uint64_t, UidTagValue> mCookieTagMap;
+
+    void SetUp() {
+        mCookieTagMap.reset(android::bpf::mapRetrieveRW(COOKIE_TAG_MAP_PATH));
+        ASSERT_TRUE(mCookieTagMap.isValid());
+    }
+
+    void TearDown() {
+        const auto deleteTestCookieEntries = [](const uint64_t& key, const UidTagValue& value,
+                                                BpfMap<uint64_t, UidTagValue>& map) {
+            if ((value.uid == TEST_UID) && (value.tag == TEST_TAG)) {
+                Result<void> res = map.deleteValue(key);
+                if (res.ok() || (res.error().code() == ENOENT)) {
+                    return Result<void>();
+                }
+                ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s\n", key,
+                      strerror(res.error().code()));
+            }
+            // Move forward to next cookie in the map.
+            return Result<void>();
+        };
+        EXPECT_RESULT_OK(mCookieTagMap.iterateWithValue(deleteTestCookieEntries));
+    }
+
+    Result<void> checkNoGarbageTagsExist() {
+        const auto checkGarbageTags = [](const uint64_t&, const UidTagValue& value,
+                                         const BpfMap<uint64_t, UidTagValue>&) -> Result<void> {
+            if ((TEST_UID == value.uid) && (TEST_TAG == value.tag)) {
+                return Error(EUCLEAN) << "Closed socket is not untagged";
+            }
+            return {};
+        };
+        return mCookieTagMap.iterateWithValue(checkGarbageTags);
+    }
+
+    bool checkMassiveSocketDestroy(int totalNumber, bool expectError) {
+        std::unique_ptr<android::netdutils::NetlinkListenerInterface> skDestroyListener;
+        auto result = android::net::TrafficController::makeSkDestroyListener();
+        if (!isOk(result)) {
+            ALOGE("Unable to create SkDestroyListener: %s", toString(result).c_str());
+        } else {
+            skDestroyListener = std::move(result.value());
+        }
+        int rxErrorCount = 0;
+        // Rx handler extracts nfgenmsg looks up and invokes registered dispatch function.
+        const auto rxErrorHandler = [&rxErrorCount](const int, const int) { rxErrorCount++; };
+        skDestroyListener->registerSkErrorHandler(rxErrorHandler);
+        int fds[totalNumber];
+        for (int i = 0; i < totalNumber; i++) {
+            fds[i] = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+            // The likely reason for a failure is running out of available file descriptors.
+            EXPECT_LE(0, fds[i]) << i << " of " << totalNumber;
+            if (fds[i] < 0) {
+                // EXPECT_LE already failed above, so test case is a failure, but we don't
+                // want potentially tens of thousands of extra failures creating and then
+                // closing all these fds cluttering up the logs.
+                totalNumber = i;
+                break;
+            };
+            libnetd_updatable_tagSocket(fds[i], TEST_TAG, TEST_UID, 1000);
+        }
+
+        // TODO: Use a separate thread that has its own fd table so we can
+        // close sockets even faster simply by terminating that thread.
+        for (int i = 0; i < totalNumber; i++) {
+            EXPECT_EQ(0, close(fds[i]));
+        }
+        // wait a bit for netlink listener to handle all the messages.
+        usleep(SOCK_CLOSE_WAIT_US);
+        if (expectError) {
+            // If ENOBUFS triggered, check it only called into the handler once, ie.
+            // that the netlink handler is not spinning.
+            int currentErrorCount = rxErrorCount;
+            // 0 error count is acceptable because the system has chances to close all sockets
+            // normally.
+            EXPECT_LE(0, rxErrorCount);
+            if (!rxErrorCount) return true;
+
+            usleep(ENOBUFS_POLL_WAIT_US);
+            EXPECT_EQ(currentErrorCount, rxErrorCount);
+        } else {
+            EXPECT_RESULT_OK(checkNoGarbageTagsExist());
+            EXPECT_EQ(0, rxErrorCount);
+        }
+        return false;
+    }
+};
+
+TEST_F(NetlinkListenerTest, TestAllSocketUntagged) {
+    checkMassiveSocketDestroy(10, false);
+    checkMassiveSocketDestroy(100, false);
+}
+
+// Disabled because flaky on blueline-userdebug; this test relies on the main thread
+// winning a race against the NetlinkListener::run() thread. There's no way to ensure
+// things will be scheduled the same way across all architectures and test environments.
+TEST_F(NetlinkListenerTest, DISABLED_TestSkDestroyError) {
+    bool needRetry = false;
+    int retryCount = 0;
+    do {
+        needRetry = checkMassiveSocketDestroy(32500, true);
+        if (needRetry) retryCount++;
+    } while (needRetry && retryCount < 3);
+    // Should review test if it can always close all sockets correctly.
+    EXPECT_GT(3, retryCount);
+}
+
+
 }  // namespace net
 }  // namespace android
diff --git a/service/native/include/Common.h b/service/native/include/Common.h
index 7c0b797..dc44845 100644
--- a/service/native/include/Common.h
+++ b/service/native/include/Common.h
@@ -27,11 +27,14 @@
 
 enum FirewallType { ALLOWLIST = INetd::FIREWALL_ALLOWLIST, DENYLIST = INetd::FIREWALL_DENYLIST };
 
+// LINT.IfChange(firewall_chain)
 enum ChildChain {
-    NONE = INetd::FIREWALL_CHAIN_NONE,
-    DOZABLE = INetd::FIREWALL_CHAIN_DOZABLE,
-    STANDBY = INetd::FIREWALL_CHAIN_STANDBY,
-    POWERSAVE = INetd::FIREWALL_CHAIN_POWERSAVE,
-    RESTRICTED = INetd::FIREWALL_CHAIN_RESTRICTED,
+    NONE = 0,
+    DOZABLE = 1,
+    STANDBY = 2,
+    POWERSAVE = 3,
+    RESTRICTED = 4,
+    LOW_POWER_STANDBY = 5,
     INVALID_CHAIN
 };
+// LINT.ThenChange(packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java)
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
index c050871..6fe117f 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -25,7 +25,6 @@
 #include "netdutils/DumpWriter.h"
 #include "netdutils/NetlinkListener.h"
 #include "netdutils/StatusOr.h"
-#include "utils/String16.h"
 
 namespace android {
 namespace net {
@@ -34,23 +33,13 @@
 
 class TrafficController {
   public:
+    static constexpr char DUMP_KEYWORD[] = "trafficcontroller";
+
     /*
      * Initialize the whole controller
      */
     netdutils::Status start();
 
-    int setCounterSet(int counterSetNum, uid_t uid, uid_t callingUid) EXCLUDES(mMutex);
-
-    /*
-     * When deleting a tag data, the qtaguid module will grab the spinlock of each
-     * related rb_tree one by one and delete the tag information, counterSet
-     * information, iface stats information and uid stats information one by one.
-     * The new eBPF implementation is done similiarly by removing the entry on
-     * each map one by one. And deleting processes are also protected by the
-     * spinlock of the map. So no additional lock is required.
-     */
-    int deleteTagData(uint32_t tag, uid_t uid, uid_t callingUid) EXCLUDES(mMutex);
-
     /*
      * Swap the stats map config from current active stats map to the idle one.
      */
@@ -84,7 +73,6 @@
 
     netdutils::Status updateUidOwnerMap(const uint32_t uid,
                                         UidOwnerMatchType matchType, IptOp op) EXCLUDES(mMutex);
-    static const String16 DUMP_KEYWORD;
 
     int toggleUidOwnerMap(ChildChain chain, bool enable) EXCLUDES(mMutex);
 
@@ -99,6 +87,7 @@
     static const char* LOCAL_STANDBY;
     static const char* LOCAL_POWERSAVE;
     static const char* LOCAL_RESTRICTED;
+    static const char* LOCAL_LOW_POWER_STANDBY;
 
   private:
     /*
@@ -160,7 +149,7 @@
      * the map right now:
      * - Entry with UID_RULES_CONFIGURATION_KEY:
      *    Store the configuration for the current uid rules. It indicates the device
-     *    is in doze/powersave/standby/restricted mode.
+     *    is in doze/powersave/standby/restricted/low power standby mode.
      * - Entry with CURRENT_STATS_MAP_CONFIGURATION_KEY:
      *    Stores the current live stats map that kernel program is writing to.
      *    Userspace can do scraping and cleaning job on the other one depending on the
diff --git a/service/native/jni/onload.cpp b/service/native/jni/onload.cpp
deleted file mode 100644
index df7c77b..0000000
--- a/service/native/jni/onload.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "TrafficControllerJni"
-
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-
-#include "utils/Log.h"
-
-namespace android {
-
-int register_com_android_server_BpfNetMaps(JNIEnv* env);
-
-extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
-    JNIEnv *env;
-    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
-        ALOGE("%s: ERROR: GetEnv failed", __func__);
-        return JNI_ERR;
-    }
-
-    if (register_com_android_server_BpfNetMaps(env) < 0)
-      return JNI_ERR;
-
-    return JNI_VERSION_1_6;
-}
-
-}; // namespace android
diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp
index 5e208d8..17ee996 100644
--- a/service/native/libs/libclat/Android.bp
+++ b/service/native/libs/libclat/Android.bp
@@ -21,8 +21,7 @@
     defaults: ["netd_defaults"],
     header_libs: [
         "bpf_connectivity_headers",
-        "bpf_headers",
-        "bpf_syscall_wrappers",
+        "libbase_headers",
     ],
     srcs: [
         "TcUtils.cpp",  // TODO: move to frameworks/libs/net
@@ -31,7 +30,6 @@
     ],
     stl: "libc++_static",
     static_libs: [
-        "libbase",
         "libip_checksum",
         "libnetdutils",  // for netdutils/UidConstants.h in bpf_shared.h
     ],
@@ -47,8 +45,6 @@
     test_suites: ["device-tests"],
     header_libs: [
         "bpf_connectivity_headers",
-        "bpf_headers",
-        "bpf_syscall_wrappers",
     ],
     srcs: [
         "TcUtilsTest.cpp",
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 9d89788..ddee275 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -16,10 +16,14 @@
 
 package com.android.server;
 
+import android.net.INetd;
+import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.system.Os;
 import android.util.Log;
 
+import com.android.modules.utils.build.SdkLevel;
+
 /**
  * BpfNetMaps is responsible for providing traffic controller relevant functionality.
  *
@@ -27,86 +31,119 @@
  */
 public class BpfNetMaps {
     private static final String TAG = "BpfNetMaps";
+    private final INetd mNetd;
+    // Use legacy netd for releases before T.
+    private static final boolean USE_NETD = !SdkLevel.isAtLeastT();
+    private static boolean sInitialized = false;
 
-    static {
-        System.loadLibrary("traffic_controller_jni");
-        native_init();
+    /**
+     * Initializes the class if it is not already initialized. This method will open maps but not
+     * cause any other effects. This method may be called multiple times on any thread.
+     */
+    private static synchronized void ensureInitialized() {
+        if (sInitialized) return;
+        if (!USE_NETD) {
+            System.loadLibrary("service-connectivity");
+            native_init();
+        }
+        sInitialized = true;
     }
 
-   /**
-    * Add naughty app bandwidth rule for specific app
-    *
-    * @param uid uid of target app
-    * @throws ServiceSpecificException in case of failure, with an error code indicating the
-    *         cause of the failure.
-    */
-    public void addNaughtyApp(final int uid) {
+    public BpfNetMaps(INetd netd) {
+        ensureInitialized();
+        mNetd = netd;
+    }
+
+    private void maybeThrow(final int err, final String msg) {
+        if (err != 0) {
+            throw new ServiceSpecificException(err, msg + ": " + Os.strerror(err));
+        }
+    }
+
+    /**
+     * Add naughty app bandwidth rule for specific app
+     *
+     * @param uid uid of target app
+     * @throws RemoteException when netd has crashed.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    public void addNaughtyApp(final int uid) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.bandwidthAddNaughtyApp(uid);
+            return;
+        }
         final int err = native_addNaughtyApp(uid);
-        if (err != 0) {
-            throw new ServiceSpecificException(err, "Unable to add naughty app: "
-                            + Os.strerror(err));
-        }
+        maybeThrow(err, "Unable to add naughty app");
     }
 
-   /**
-    * Remove naughty app bandwidth rule for specific app
-    *
-    * @param uid uid of target app
-    * @throws ServiceSpecificException in case of failure, with an error code indicating the
-    *         cause of the failure.
-    */
-    public void removeNaughtyApp(final int uid) {
+    /**
+     * Remove naughty app bandwidth rule for specific app
+     *
+     * @param uid uid of target app
+     * @throws RemoteException when netd has crashed.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    public void removeNaughtyApp(final int uid) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.bandwidthRemoveNaughtyApp(uid);
+            return;
+        }
         final int err = native_removeNaughtyApp(uid);
-        if (err != 0) {
-            throw new ServiceSpecificException(err, "Unable to remove naughty app: "
-                            + Os.strerror(err));
-        }
+        maybeThrow(err, "Unable to remove naughty app");
     }
 
-   /**
-    * Add nice app bandwidth rule for specific app
-    *
-    * @param uid uid of target app
-    * @throws ServiceSpecificException in case of failure, with an error code indicating the
-    *         cause of the failure.
-    */
-    public void addNiceApp(final int uid) {
+    /**
+     * Add nice app bandwidth rule for specific app
+     *
+     * @param uid uid of target app
+     * @throws RemoteException when netd has crashed.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    public void addNiceApp(final int uid) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.bandwidthAddNiceApp(uid);
+            return;
+        }
         final int err = native_addNiceApp(uid);
-        if (err != 0) {
-            throw new ServiceSpecificException(err, "Unable to add nice app: "
-                            + Os.strerror(err));
-        }
+        maybeThrow(err, "Unable to add nice app");
     }
 
-   /**
-    * Remove nice app bandwidth rule for specific app
-    *
-    * @param uid uid of target app
-    * @throws ServiceSpecificException in case of failure, with an error code indicating the
-    *         cause of the failure.
-    */
-    public void removeNiceApp(final int uid) {
+    /**
+     * Remove nice app bandwidth rule for specific app
+     *
+     * @param uid uid of target app
+     * @throws RemoteException when netd has crashed.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    public void removeNiceApp(final int uid) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.bandwidthRemoveNiceApp(uid);
+            return;
+        }
         final int err = native_removeNiceApp(uid);
-        if (err != 0) {
-            throw new ServiceSpecificException(err, "Unable to remove nice app: "
-                            + Os.strerror(err));
-        }
+        maybeThrow(err, "Unable to remove nice app");
     }
 
-   /**
-    * Set target firewall child chain
-    *
-    * @param childChain target chain to enable
-    * @param enable whether to enable or disable child chain.
-    * @throws ServiceSpecificException in case of failure, with an error code indicating the
-    *         cause of the failure.
-    */
-    public void setChildChain(final int childChain, final boolean enable) {
-        final int err = native_setChildChain(childChain, enable);
-        if (err != 0) {
-            throw new ServiceSpecificException(-err, "Unable to set child chain: "
-                            + Os.strerror(-err));
+    /**
+     * Set target firewall child chain
+     *
+     * @param childChain target chain to enable
+     * @param enable     whether to enable or disable child chain.
+     * @throws RemoteException when netd has crashed.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    public void setChildChain(final int childChain, final boolean enable) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.firewallEnableChildChain(childChain, enable);
+            return;
         }
+        final int err = native_setChildChain(childChain, enable);
+        maybeThrow(err, "Unable to set child chain");
     }
 
     /**
@@ -114,16 +151,21 @@
      *
      * The chain may be an allowlist chain or a denylist chain. A denylist chain contains DROP
      * rules for the specified UIDs and a RETURN rule at the end. An allowlist chain contains RETURN
-     * rules for the system UID range (0 to {@code UID_APP} - 1), RETURN rules for for the specified
+     * rules for the system UID range (0 to {@code UID_APP} - 1), RETURN rules for the specified
      * UIDs, and a DROP rule at the end. The chain will be created if it does not exist.
      *
-     * @param chainName The name of the chain to replace.
+     * @param chainName   The name of the chain to replace.
      * @param isAllowlist Whether this is an allowlist or denylist chain.
-     * @param uids The list of UIDs to allow/deny.
-     * @return true if the chain was successfully replaced, false otherwise.
+     * @param uids        The list of UIDs to allow/deny.
+     * @return 0 if the chain was successfully replaced, errno otherwise.
+     * @throws RemoteException when netd has crashed.
      */
     public int replaceUidChain(final String chainName, final boolean isAllowlist,
-            final int[] uids) {
+            final int[] uids) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.firewallReplaceUidChain(chainName, isAllowlist, uids);
+            return 0;
+        }
         final int err = native_replaceUidChain(chainName, isAllowlist, uids);
         if (err != 0) {
             Log.e(TAG, "replaceUidChain failed: " + Os.strerror(-err));
@@ -131,22 +173,24 @@
         return -err;
     }
 
-   /**
-    * Set firewall rule for uid
-    *
-    * @param childChain target chain
-    * @param uid uid to allow/deny
-    * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
-    * @throws ServiceSpecificException in case of failure, with an error code indicating the
-    *         cause of the failure.
-    */
-    public void setUidRule(final int childChain, final int uid,
-            final int firewallRule) {
-        final int err = native_setUidRule(childChain, uid, firewallRule);
-        if (err != 0) {
-            throw new ServiceSpecificException(-err, "Unable to set uid rule: "
-                            + Os.strerror(-err));
+    /**
+     * Set firewall rule for uid
+     *
+     * @param childChain   target chain
+     * @param uid          uid to allow/deny
+     * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
+     * @throws RemoteException when netd has crashed.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    public void setUidRule(final int childChain, final int uid, final int firewallRule)
+            throws RemoteException {
+        if (USE_NETD) {
+            mNetd.firewallSetUidRule(childChain, uid, firewallRule);
+            return;
         }
+        final int err = native_setUidRule(childChain, uid, firewallRule);
+        maybeThrow(err, "Unable to set uid rule");
     }
 
     /**
@@ -160,17 +204,19 @@
      * instead. Otherwise calling this method will not affect existing rules set on other UIDs.
      *
      * @param ifName the name of the interface on which the filtering rules will allow packets to
-              be received.
-     * @param uids an array of UIDs which the filtering rules will be set
+     *               be received.
+     * @param uids   an array of UIDs which the filtering rules will be set
+     * @throws RemoteException when netd has crashed.
      * @throws ServiceSpecificException in case of failure, with an error code indicating the
-     *         cause of the failure.
+     *                                  cause of the failure.
      */
-    public void addUidInterfaceRules(final String ifName, final int[] uids) {
-        final int err = native_addUidInterfaceRules(ifName, uids);
-        if (err != 0) {
-            throw new ServiceSpecificException(err, "Unable to add uid interface rules: "
-                            + Os.strerror(err));
+    public void addUidInterfaceRules(final String ifName, final int[] uids) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.firewallAddUidInterfaceRules(ifName, uids);
+            return;
         }
+        final int err = native_addUidInterfaceRules(ifName, uids);
+        maybeThrow(err, "Unable to add uid interface rules");
     }
 
     /**
@@ -180,68 +226,51 @@
      * by addUidInterfaceRules(). Ignore any uid which does not have filtering rule.
      *
      * @param uids an array of UIDs from which the filtering rules will be removed
+     * @throws RemoteException when netd has crashed.
      * @throws ServiceSpecificException in case of failure, with an error code indicating the
-     *         cause of the failure.
+     *                                  cause of the failure.
      */
-    public void removeUidInterfaceRules(final int[] uids) {
+    public void removeUidInterfaceRules(final int[] uids) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.firewallRemoveUidInterfaceRules(uids);
+            return;
+        }
         final int err = native_removeUidInterfaceRules(uids);
-        if (err != 0) {
-            throw new ServiceSpecificException(err, "Unable to remove uid interface rules: "
-                            + Os.strerror(err));
-        }
-    }
-
-   /**
-    * Request netd to change the current active network stats map.
-    * @throws ServiceSpecificException in case of failure, with an error code indicating the
-    *         cause of the failure.
-    */
-    public void swapActiveStatsMap() {
-        final int err = native_swapActiveStatsMap();
-        if (err != 0) {
-            throw new ServiceSpecificException(err, "Unable to swap active stats map: "
-                            + Os.strerror(err));
-        }
-    }
-
-   /**
-    * Assigns android.permission.INTERNET and/or android.permission.UPDATE_DEVICE_STATS to the uids
-    * specified. Or remove all permissions from the uids.
-    *
-    * @param permission The permission to grant, it could be either PERMISSION_INTERNET and/or
-    *                   PERMISSION_UPDATE_DEVICE_STATS. If the permission is NO_PERMISSIONS, then
-    *                   revoke all permissions for the uids.
-    * @param uids uid of users to grant permission
-    */
-    public void setNetPermForUids(final int permission, final int[] uids) {
-        native_setPermissionForUids(permission, uids);
+        maybeThrow(err, "Unable to remove uid interface rules");
     }
 
     /**
-     * Set counter set for uid
+     * Request netd to change the current active network stats map.
      *
-     * @param counterSet either SET_DEFAULT or SET_FOREGROUND
-     * @param uid uid to foreground/background
+     * @throws RemoteException when netd has crashed.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
      */
-    public int setCounterSet(final int counterSet, final int uid) {
-        final int err = native_setCounterSet(counterSet, uid);
-        if (err != 0) {
-            Log.e(TAG, "setCounterSet failed: " + Os.strerror(-err));
+    public void swapActiveStatsMap() throws RemoteException {
+        if (USE_NETD) {
+            mNetd.trafficSwapActiveStatsMap();
+            return;
         }
-        return -err;
+        final int err = native_swapActiveStatsMap();
+        maybeThrow(err, "Unable to swap active stats map");
     }
 
     /**
-     * Reset Uid stats
-     * @param tag default 0
-     * @param uid given uid to be clear
+     * Assigns android.permission.INTERNET and/or android.permission.UPDATE_DEVICE_STATS to the uids
+     * specified. Or remove all permissions from the uids.
+     *
+     * @param permissions The permission to grant, it could be either PERMISSION_INTERNET and/or
+     *                    PERMISSION_UPDATE_DEVICE_STATS. If the permission is NO_PERMISSIONS, then
+     *                    revoke all permissions for the uids.
+     * @param uids        uid of users to grant permission
+     * @throws RemoteException when netd has crashed.
      */
-    public int deleteTagData(final int tag, final int uid) {
-        final int err = native_deleteTagData(tag, uid);
-        if (err != 0) {
-            Log.e(TAG, "deleteTagData failed: " + Os.strerror(-err));
+    public void setNetPermForUids(final int permissions, final int[] uids) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.trafficSetNetPermForUids(permissions, uids);
+            return;
         }
-        return -err;
+        native_setPermissionForUids(permissions, uids);
     }
 
     private static native void native_init();
@@ -255,7 +284,5 @@
     private native int native_addUidInterfaceRules(String ifName, int[] uids);
     private native int native_removeUidInterfaceRules(int[] uids);
     private native int native_swapActiveStatsMap();
-    private native void native_setPermissionForUids(int permission, int[] uids);
-    private native int native_setCounterSet(int counterSet, int uid);
-    private native int native_deleteTagData(int tag, int uid);
+    private native void native_setPermissionForUids(int permissions, int[] uids);
 }
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index fa7dfa9..6024a2a 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -90,6 +90,7 @@
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.VPN_UID;
 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
+import static android.system.OsConstants.ETH_P_ALL;
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.IPPROTO_UDP;
 
@@ -191,11 +192,13 @@
 import android.net.netd.aidl.NativeUidRangeConfig;
 import android.net.networkstack.ModuleNetworkStackClient;
 import android.net.networkstack.NetworkStackClientBase;
+import android.net.networkstack.aidl.NetworkMonitorParameters;
 import android.net.resolv.aidl.DnsHealthEventParcel;
 import android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener;
 import android.net.resolv.aidl.Nat64PrefixEventParcel;
 import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
 import android.net.shared.PrivateDnsConfig;
+import android.net.util.InterfaceParams;
 import android.net.util.MultinetworkPolicyTracker;
 import android.os.BatteryStatsManager;
 import android.os.Binder;
@@ -247,8 +250,10 @@
 import com.android.net.module.util.LocationPermissionChecker;
 import com.android.net.module.util.NetworkCapabilitiesUtils;
 import com.android.net.module.util.PermissionUtils;
+import com.android.net.module.util.TcUtils;
 import com.android.net.module.util.netlink.InetDiagMessage;
 import com.android.server.connectivity.AutodestructReference;
+import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
 import com.android.server.connectivity.ConnectivityFlags;
 import com.android.server.connectivity.DnsManager;
 import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
@@ -272,6 +277,7 @@
 import libcore.io.IoUtils;
 
 import java.io.FileDescriptor;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.Writer;
 import java.net.Inet4Address;
@@ -396,6 +402,7 @@
     private NetworkStatsManager mStatsManager;
     private NetworkPolicyManager mPolicyManager;
     private final NetdCallback mNetdCallback;
+    private final BpfNetMaps mBpfNetMaps;
 
     /**
      * TestNetworkService (lazily) created upon first usage. Locked to prevent creation of multiple
@@ -438,8 +445,6 @@
      * Requests that don't code for a per-app preference use PREFERENCE_ORDER_INVALID.
      * The default request uses PREFERENCE_ORDER_DEFAULT.
      */
-    // Bound for the lowest valid preference order.
-    static final int PREFERENCE_ORDER_LOWEST = 999;
     // Used when sending to netd to code for "no order".
     static final int PREFERENCE_ORDER_NONE = 0;
     // Order for requests that don't code for a per-app preference. As it is
@@ -447,11 +452,6 @@
     // PREFERENCE_ORDER_NONE when sending to netd.
     @VisibleForTesting
     static final int PREFERENCE_ORDER_INVALID = Integer.MAX_VALUE;
-    // Order for the default internet request. Since this must always have the
-    // lowest priority, its value is larger than the largest acceptable value. As
-    // it is out of the valid range, the corresponding order should be
-    // PREFERENCE_ORDER_NONE when sending to netd.
-    static final int PREFERENCE_ORDER_DEFAULT = 1000;
     // As a security feature, VPNs have the top priority.
     static final int PREFERENCE_ORDER_VPN = 0; // Netd supports only 0 for VPN.
     // Order of per-app OEM preference. See {@link #setOemNetworkPreference}.
@@ -466,6 +466,13 @@
     // See {@link ConnectivitySettingsManager#setMobileDataPreferredUids}
     @VisibleForTesting
     static final int PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED = 30;
+    // Preference order that signifies the network shouldn't be set as a default network for
+    // the UIDs, only give them access to it. TODO : replace this with a boolean
+    // in NativeUidRangeConfig
+    @VisibleForTesting
+    static final int PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT = 999;
+    // Bound for the lowest valid preference order.
+    static final int PREFERENCE_ORDER_LOWEST = 999;
 
     /**
      * used internally to clear a wakelock when transitioning
@@ -593,6 +600,13 @@
     // Handle private DNS validation status updates.
     private static final int EVENT_PRIVATE_DNS_VALIDATION_UPDATE = 38;
 
+    /**
+     * used to remove a network request, either a listener or a real request and call unavailable
+     * arg1 = UID of caller
+     * obj  = NetworkRequest
+     */
+    private static final int EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE = 39;
+
      /**
       * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has
       * been tested.
@@ -699,6 +713,11 @@
     private static final int EVENT_SET_TEST_ALLOW_BAD_WIFI_UNTIL = 55;
 
     /**
+     * Used internally when INGRESS_RATE_LIMIT_BYTES_PER_SECOND setting changes.
+     */
+    private static final int EVENT_INGRESS_RATE_LIMIT_CHANGED = 56;
+
+    /**
      * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
      * should be shown.
      */
@@ -715,6 +734,18 @@
      */
     private static final long MAX_TEST_ALLOW_BAD_WIFI_UNTIL_MS = 5 * 60 * 1000L;
 
+    /**
+     * The priority of the tc police rate limiter -- smaller value is higher priority.
+     * This value needs to be coordinated with PRIO_CLAT, PRIO_TETHER4, and PRIO_TETHER6.
+     */
+    private static final short TC_PRIO_POLICE = 1;
+
+    /**
+     * The BPF program attached to the tc-police hook to account for to-be-dropped traffic.
+     */
+    private static final String TC_POLICE_BPF_PROG_PATH =
+            "/sys/fs/bpf/prog_netd_schedact_ingress_account";
+
     private static String eventName(int what) {
         return sMagicDecoderRing.get(what, Integer.toString(what));
     }
@@ -760,6 +791,7 @@
     private Set<String> mWolSupportedInterfaces;
 
     private final TelephonyManager mTelephonyManager;
+    private final CarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
     private final AppOpsManager mAppOpsManager;
 
     private final LocationPermissionChecker mLocationPermissionChecker;
@@ -804,6 +836,12 @@
     final Map<IBinder, ConnectivityDiagnosticsCallbackInfo> mConnectivityDiagnosticsCallbacks =
             new HashMap<>();
 
+    // Rate limit applicable to all internet capable networks (-1 = disabled). This value is
+    // configured via {@link
+    // ConnectivitySettingsManager#INGRESS_RATE_LIMIT_BYTES_PER_SECOND}
+    // Only the handler thread is allowed to access this field.
+    private long mIngressRateLimit = -1;
+
     /**
      * Implements support for the legacy "one network per network type" model.
      *
@@ -1329,12 +1367,75 @@
         }
 
         /**
+         * @see CarrierPrivilegeAuthenticator
+         */
+        public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
+                @NonNull final Context context, @NonNull final TelephonyManager tm) {
+            if (SdkLevel.isAtLeastT()) {
+                return new CarrierPrivilegeAuthenticator(context, tm);
+            } else {
+                return null;
+            }
+        }
+
+        /**
          * @see DeviceConfigUtils#isFeatureEnabled
          */
         public boolean isFeatureEnabled(Context context, String name, boolean defaultEnabled) {
             return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name,
                     TETHERING_MODULE_NAME, defaultEnabled);
         }
+
+        /**
+         * Get the BpfNetMaps implementation to use in ConnectivityService.
+         * @param netd
+         * @return BpfNetMaps implementation.
+         */
+        public BpfNetMaps getBpfNetMaps(INetd netd) {
+            return new BpfNetMaps(netd);
+        }
+
+        /**
+         * Wraps {@link TcUtils#tcFilterAddDevIngressPolice}
+         */
+        public void enableIngressRateLimit(String iface, long rateInBytesPerSecond) {
+            final InterfaceParams params = InterfaceParams.getByName(iface);
+            if (params == null) {
+                // the interface might have disappeared.
+                logw("Failed to get interface params for interface " + iface);
+                return;
+            }
+            try {
+                // converting rateInBytesPerSecond from long to int is safe here because the
+                // setting's range is limited to INT_MAX.
+                // TODO: add long/uint64 support to tcFilterAddDevIngressPolice.
+                TcUtils.tcFilterAddDevIngressPolice(params.index, TC_PRIO_POLICE, (short) ETH_P_ALL,
+                        (int) rateInBytesPerSecond, TC_POLICE_BPF_PROG_PATH);
+            } catch (IOException e) {
+                loge("TcUtils.tcFilterAddDevIngressPolice(ifaceIndex=" + params.index
+                        + ", PRIO_POLICE, ETH_P_ALL, rateInBytesPerSecond="
+                        + rateInBytesPerSecond + ", bpfProgPath=" + TC_POLICE_BPF_PROG_PATH
+                        + ") failure: ", e);
+            }
+        }
+
+        /**
+         * Wraps {@link TcUtils#tcFilterDelDev}
+         */
+        public void disableIngressRateLimit(String iface) {
+            final InterfaceParams params = InterfaceParams.getByName(iface);
+            if (params == null) {
+                // the interface might have disappeared.
+                logw("Failed to get interface params for interface " + iface);
+                return;
+            }
+            try {
+                TcUtils.tcFilterDelDev(params.index, true, TC_PRIO_POLICE, (short) ETH_P_ALL);
+            } catch (IOException e) {
+                loge("TcUtils.tcFilterDelDev(ifaceIndex=" + params.index
+                        + ", ingress=true, PRIO_POLICE, ETH_P_ALL) failure: ", e);
+            }
+        }
     }
 
     public ConnectivityService(Context context) {
@@ -1403,9 +1504,12 @@
         mProxyTracker = mDeps.makeProxyTracker(mContext, mHandler);
 
         mNetd = netd;
+        mBpfNetMaps = mDeps.getBpfNetMaps(netd);
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
+        mCarrierPrivilegeAuthenticator =
+                mDeps.makeCarrierPrivilegeAuthenticator(mContext, mTelephonyManager);
 
         // To ensure uid state is synchronized with Network Policy, register for
         // NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
@@ -1430,7 +1534,7 @@
 
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
 
-        mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
+        mPermissionMonitor = new PermissionMonitor(mContext, mNetd, mBpfNetMaps);
 
         mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
         // Listen for user add/removes to inform PermissionMonitor.
@@ -1506,6 +1610,9 @@
         } catch (ErrnoException e) {
             loge("Unable to create DscpPolicyTracker");
         }
+
+        mIngressRateLimit = ConnectivitySettingsManager.getIngressRateLimitInBytesPerSecond(
+                mContext);
     }
 
     private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
@@ -1576,6 +1683,11 @@
         mHandler.sendEmptyMessage(EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED);
     }
 
+    @VisibleForTesting
+    void updateIngressRateLimit() {
+        mHandler.sendEmptyMessage(EVENT_INGRESS_RATE_LIMIT_CHANGED);
+    }
+
     private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, int id) {
         final boolean enable = mContext.getResources().getBoolean(id);
         handleAlwaysOnNetworkRequest(networkRequest, enable);
@@ -1637,6 +1749,12 @@
         mSettingsObserver.observe(
                 Settings.Secure.getUriFor(ConnectivitySettingsManager.MOBILE_DATA_PREFERRED_UIDS),
                 EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED);
+
+        // Watch for ingress rate limit changes.
+        mSettingsObserver.observe(
+                Settings.Secure.getUriFor(
+                        ConnectivitySettingsManager.INGRESS_RATE_LIMIT_BYTES_PER_SECOND),
+                EVENT_INGRESS_RATE_LIMIT_CHANGED);
     }
 
     private void registerPrivateDnsSettingsCallbacks() {
@@ -2046,6 +2164,19 @@
         }
     }
 
+    @Override
+    @Nullable
+    public LinkProperties redactLinkPropertiesForPackage(@NonNull LinkProperties lp, int uid,
+            @NonNull String packageName, @Nullable String callingAttributionTag) {
+        Objects.requireNonNull(packageName);
+        Objects.requireNonNull(lp);
+        enforceNetworkStackOrSettingsPermission();
+        if (!checkAccessPermission(-1 /* pid */, uid)) {
+            return null;
+        }
+        return linkPropertiesRestrictedForCallerPermissions(lp, -1 /* callerPid */, uid);
+    }
+
     private NetworkCapabilities getNetworkCapabilitiesInternal(Network network) {
         return getNetworkCapabilitiesInternal(getNetworkAgentInfoForNetwork(network));
     }
@@ -2069,13 +2200,34 @@
                 getCallingPid(), mDeps.getCallingUid(), callingPackageName, callingAttributionTag);
     }
 
+    @Override
+    public NetworkCapabilities redactNetworkCapabilitiesForPackage(@NonNull NetworkCapabilities nc,
+            int uid, @NonNull String packageName, @Nullable String callingAttributionTag) {
+        Objects.requireNonNull(nc);
+        Objects.requireNonNull(packageName);
+        enforceNetworkStackOrSettingsPermission();
+        if (!checkAccessPermission(-1 /* pid */, uid)) {
+            return null;
+        }
+        return createWithLocationInfoSanitizedIfNecessaryWhenParceled(
+                networkCapabilitiesRestrictedForCallerPermissions(nc, -1 /* callerPid */, uid),
+                true /* includeLocationSensitiveInfo */, -1 /* callingPid */, uid, packageName,
+                callingAttributionTag);
+    }
+
     @VisibleForTesting
     NetworkCapabilities networkCapabilitiesRestrictedForCallerPermissions(
             NetworkCapabilities nc, int callerPid, int callerUid) {
+        // Note : here it would be nice to check ACCESS_NETWORK_STATE and return null, but
+        // this would be expensive (one more permission check every time any NC callback is
+        // sent) and possibly dangerous : apps normally can't lose ACCESS_NETWORK_STATE, if
+        // it happens for some reason (e.g. the package is uninstalled while CS is trying to
+        // send the callback) it would crash the system server with NPE.
         final NetworkCapabilities newNc = new NetworkCapabilities(nc);
         if (!checkSettingsPermission(callerPid, callerUid)) {
             newNc.setUids(null);
             newNc.setSSID(null);
+            // TODO: Processes holding NETWORK_FACTORY should be able to see the underlying networks
             newNc.setUnderlyingNetworks(null);
         }
         if (newNc.getNetworkSpecifier() != null) {
@@ -2084,6 +2236,7 @@
         newNc.setAdministratorUids(new int[0]);
         if (!checkAnyPermissionOf(
                 callerPid, callerUid, android.Manifest.permission.NETWORK_FACTORY)) {
+            newNc.setAccessUids(new ArraySet<>());
             newNc.setSubscriptionIds(Collections.emptySet());
         }
 
@@ -2092,7 +2245,7 @@
 
     /**
      * Wrapper used to cache the permission check results performed for the corresponding
-     * app. This avoid performing multiple permission checks for different fields in
+     * app. This avoids performing multiple permission checks for different fields in
      * NetworkCapabilities.
      * Note: This wrapper does not support any sort of invalidation and thus must not be
      * persistent or long-lived. It may only be used for the time necessary to
@@ -2220,6 +2373,8 @@
                 includeLocationSensitiveInfo);
         final NetworkCapabilities newNc = new NetworkCapabilities(nc, redactions);
         // Reset owner uid if not destined for the owner app.
+        // TODO : calling UID is redacted because apps should generally not know what UID is
+        // bringing up the VPN, but this should not apply to some very privileged apps like settings
         if (callingUid != nc.getOwnerUid()) {
             newNc.setOwnerUid(INVALID_UID);
             return newNc;
@@ -2245,9 +2400,15 @@
         return newNc;
     }
 
+    @NonNull
     private LinkProperties linkPropertiesRestrictedForCallerPermissions(
             LinkProperties lp, int callerPid, int callerUid) {
         if (lp == null) return new LinkProperties();
+        // Note : here it would be nice to check ACCESS_NETWORK_STATE and return null, but
+        // this would be expensive (one more permission check every time any LP callback is
+        // sent) and possibly dangerous : apps normally can't lose ACCESS_NETWORK_STATE, if
+        // it happens for some reason (e.g. the package is uninstalled while CS is trying to
+        // send the callback) it would crash the system server with NPE.
 
         // Only do a permission check if sanitization is needed, to avoid unnecessary binder calls.
         final boolean needsSanitization =
@@ -2618,6 +2779,11 @@
                 "ConnectivityService");
     }
 
+    private boolean checkAccessPermission(int pid, int uid) {
+        return mContext.checkPermission(android.Manifest.permission.ACCESS_NETWORK_STATE, pid, uid)
+                == PERMISSION_GRANTED;
+    }
+
     /**
      * Performs a strict and comprehensive check of whether a calling package is allowed to
      * change the state of network, as the condition differs for pre-M, M+, and
@@ -3333,18 +3499,8 @@
 
             switch (msg.what) {
                 case NetworkAgent.EVENT_NETWORK_CAPABILITIES_CHANGED: {
-                    NetworkCapabilities networkCapabilities = (NetworkCapabilities) arg.second;
-                    if (networkCapabilities.hasConnectivityManagedCapability()) {
-                        Log.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
-                    }
-                    if (networkCapabilities.hasTransport(TRANSPORT_TEST)) {
-                        // Make sure the original object is not mutated. NetworkAgent normally
-                        // makes a copy of the capabilities when sending the message through
-                        // the Messenger, but if this ever changes, not making a defensive copy
-                        // here will give attack vectors to clients using this code path.
-                        networkCapabilities = new NetworkCapabilities(networkCapabilities);
-                        networkCapabilities.restrictCapabilitiesForTestNetwork(nai.creatorUid);
-                    }
+                    final NetworkCapabilities networkCapabilities = new NetworkCapabilities(
+                            (NetworkCapabilities) arg.second);
                     processCapabilitiesFromAgent(nai, networkCapabilities);
                     updateCapabilities(nai.getCurrentScore(), nai, networkCapabilities);
                     break;
@@ -4065,6 +4221,11 @@
             // for an unnecessarily long time.
             destroyNativeNetwork(nai);
             mDnsManager.removeNetwork(nai.network);
+
+            // clean up tc police filters on interface.
+            if (canNetworkBeRateLimited(nai) && mIngressRateLimit >= 0) {
+                mDeps.disableIngressRateLimit(nai.linkProperties.getInterfaceName());
+            }
         }
         mNetIdManager.releaseNetId(nai.network.getNetId());
         nai.onNetworkDestroyed();
@@ -4140,6 +4301,15 @@
         }
     }
 
+    private boolean hasCarrierPrivilegeForNetworkCaps(final int callingUid,
+            @NonNull final NetworkCapabilities caps) {
+        if (SdkLevel.isAtLeastT() && mCarrierPrivilegeAuthenticator != null) {
+            return mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                    callingUid, caps);
+        }
+        return false;
+    }
+
     private void handleRegisterNetworkRequestWithIntent(@NonNull final Message msg) {
         final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
         // handleRegisterNetworkRequestWithIntent() doesn't apply to multilayer requests.
@@ -4163,6 +4333,7 @@
 
     private void handleRegisterNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
         ensureRunningOnConnectivityServiceThread();
+        NetworkRequest requestToBeReleased = null;
         for (final NetworkRequestInfo nri : nris) {
             mNetworkRequestInfoLogs.log("REGISTER " + nri);
             checkNrisConsistency(nri);
@@ -4177,7 +4348,15 @@
                         }
                     }
                 }
+                if (req.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
+                    if (!hasCarrierPrivilegeForNetworkCaps(nri.mUid, req.networkCapabilities)
+                            && !checkConnectivityRestrictedNetworksPermission(
+                                    nri.mPid, nri.mUid)) {
+                        requestToBeReleased = req;
+                    }
+                }
             }
+
             // If this NRI has a satisfier already, it is replacing an older request that
             // has been removed. Track it.
             final NetworkRequest activeRequest = nri.getActiveRequest();
@@ -4187,6 +4366,11 @@
             }
         }
 
+        if (requestToBeReleased != null) {
+            releaseNetworkRequestAndCallOnUnavailable(requestToBeReleased);
+            return;
+        }
+
         if (mFlags.noRematchAllRequestsOnRegister()) {
             rematchNetworksAndRequests(nris);
         } else {
@@ -5026,6 +5210,11 @@
                             /* callOnUnavailable */ false);
                     break;
                 }
+                case EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE: {
+                    handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1,
+                            /* callOnUnavailable */ true);
+                    break;
+                }
                 case EVENT_SET_ACCEPT_UNVALIDATED: {
                     Network network = (Network) msg.obj;
                     handleSetAcceptUnvalidated(network, toBool(msg.arg1), toBool(msg.arg2));
@@ -5104,6 +5293,9 @@
                     final long timeMs = ((Long) msg.obj).longValue();
                     mMultinetworkPolicyTracker.setTestAllowBadWifiUntil(timeMs);
                     break;
+                case EVENT_INGRESS_RATE_LIMIT_CHANGED:
+                    handleIngressRateLimitChanged();
+                    break;
             }
         }
     }
@@ -6095,13 +6287,6 @@
         }
     }
 
-    private void ensureRequestableCapabilities(NetworkCapabilities networkCapabilities) {
-        final String badCapability = networkCapabilities.describeFirstNonRequestableCapability();
-        if (badCapability != null) {
-            throw new IllegalArgumentException("Cannot request network with " + badCapability);
-        }
-    }
-
     // This checks that the passed capabilities either do not request a
     // specific SSID/SignalStrength, or the calling app has permission to do so.
     private void ensureSufficientPermissionsForRequest(NetworkCapabilities nc,
@@ -6159,7 +6344,7 @@
         nai.onSignalStrengthThresholdsUpdated(thresholdsArray);
     }
 
-    private void ensureValidNetworkSpecifier(NetworkCapabilities nc) {
+    private static void ensureValidNetworkSpecifier(NetworkCapabilities nc) {
         if (nc == null) {
             return;
         }
@@ -6172,11 +6357,22 @@
         }
     }
 
-    private void ensureValid(NetworkCapabilities nc) {
+    private static void ensureListenableCapabilities(@NonNull final NetworkCapabilities nc) {
         ensureValidNetworkSpecifier(nc);
         if (nc.isPrivateDnsBroken()) {
             throw new IllegalArgumentException("Can't request broken private DNS");
         }
+        if (nc.hasAccessUids()) {
+            throw new IllegalArgumentException("Can't request access UIDs");
+        }
+    }
+
+    private void ensureRequestableCapabilities(@NonNull final NetworkCapabilities nc) {
+        ensureListenableCapabilities(nc);
+        final String badCapability = nc.describeFirstNonRequestableCapability();
+        if (badCapability != null) {
+            throw new IllegalArgumentException("Cannot request network with " + badCapability);
+        }
     }
 
     // TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
@@ -6271,7 +6467,6 @@
         if (timeoutMs < 0) {
             throw new IllegalArgumentException("Bad timeout specified");
         }
-        ensureValid(networkCapabilities);
 
         final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
                 nextNetworkRequestId(), reqType);
@@ -6330,12 +6525,24 @@
     private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
             String callingPackageName, String callingAttributionTag) {
         if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
-            enforceConnectivityRestrictedNetworksPermission();
+            if (!networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
+                enforceConnectivityRestrictedNetworksPermission();
+            }
         } else {
             enforceChangePermission(callingPackageName, callingAttributionTag);
         }
     }
 
+    private boolean checkConnectivityRestrictedNetworksPermission(int callerPid, int callerUid) {
+        if (checkAnyPermissionOf(callerPid, callerUid,
+                android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS)
+                || checkAnyPermissionOf(callerPid, callerUid,
+                android.Manifest.permission.CONNECTIVITY_INTERNAL)) {
+            return true;
+        }
+        return false;
+    }
+
     @Override
     public boolean requestBandwidthUpdate(Network network) {
         enforceAccessPermission();
@@ -6399,7 +6606,6 @@
         ensureRequestableCapabilities(networkCapabilities);
         ensureSufficientPermissionsForRequest(networkCapabilities,
                 Binder.getCallingPid(), callingUid, callingPackageName);
-        ensureValidNetworkSpecifier(networkCapabilities);
         restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities,
                 callingUid, callingPackageName);
 
@@ -6468,7 +6674,7 @@
         // There is no need to do this for requests because an app without CHANGE_NETWORK_STATE
         // can't request networks.
         restrictBackgroundRequestForCaller(nc);
-        ensureValid(nc);
+        ensureListenableCapabilities(nc);
 
         NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
                 NetworkRequest.Type.LISTEN);
@@ -6490,7 +6696,7 @@
         if (!hasWifiNetworkListenPermission(networkCapabilities)) {
             enforceAccessPermission();
         }
-        ensureValid(networkCapabilities);
+        ensureListenableCapabilities(networkCapabilities);
         ensureSufficientPermissionsForRequest(networkCapabilities,
                 Binder.getCallingPid(), callingUid, callingPackageName);
         final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
@@ -6518,6 +6724,13 @@
                 EVENT_RELEASE_NETWORK_REQUEST, mDeps.getCallingUid(), 0, networkRequest));
     }
 
+    private void releaseNetworkRequestAndCallOnUnavailable(NetworkRequest networkRequest) {
+        ensureNetworkRequestHasType(networkRequest);
+        mHandler.sendMessage(mHandler.obtainMessage(
+                EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE, mDeps.getCallingUid(), 0,
+                networkRequest));
+    }
+
     private void handleRegisterNetworkProvider(NetworkProviderInfo npi) {
         if (mNetworkProviderInfos.containsKey(npi.messenger)) {
             // Avoid creating duplicates. even if an app makes a direct AIDL call.
@@ -6915,28 +7128,18 @@
             LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
             NetworkScore currentScore, NetworkAgentConfig networkAgentConfig, int providerId,
             int uid) {
-        if (networkCapabilities.hasTransport(TRANSPORT_TEST)) {
-            // Strictly, sanitizing here is unnecessary as the capabilities will be sanitized in
-            // the call to mixInCapabilities below anyway, but sanitizing here means the NAI never
-            // sees capabilities that may be malicious, which might prevent mistakes in the future.
-            networkCapabilities = new NetworkCapabilities(networkCapabilities);
-            networkCapabilities.restrictCapabilitiesForTestNetwork(uid);
-        }
 
-        LinkProperties lp = new LinkProperties(linkProperties);
-
-        final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
+        // At this point the capabilities/properties are untrusted and unverified, e.g. checks that
+        // the capabilities' access UID comply with security limitations. They will be sanitized
+        // as the NAI registration finishes, in handleRegisterNetworkAgent(). This is
+        // because some of the checks must happen on the handler thread.
         final NetworkAgentInfo nai = new NetworkAgentInfo(na,
-                new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
+                new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo),
+                linkProperties, networkCapabilities,
                 currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig),
                 this, mNetd, mDnsResolver, providerId, uid, mLingerDelayMs,
                 mQosCallbackTracker, mDeps);
 
-        // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says.
-        processCapabilitiesFromAgent(nai, nc);
-        nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
-        processLinkPropertiesFromAgent(nai, nai.linkProperties);
-
         final String extraInfo = networkInfo.getExtraInfo();
         final String name = TextUtils.isEmpty(extraInfo)
                 ? nai.networkCapabilities.getSsid() : extraInfo;
@@ -6951,8 +7154,20 @@
     }
 
     private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) {
+        if (VDBG) log("Network Monitor created for " +  nai);
+        // nai.nc and nai.lp are the same object that was passed by the network agent if the agent
+        // lives in the same process as this code (e.g. wifi), so make sure this code doesn't
+        // mutate their object
+        final NetworkCapabilities nc = new NetworkCapabilities(nai.networkCapabilities);
+        final LinkProperties lp = new LinkProperties(nai.linkProperties);
+        // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says.
+        processCapabilitiesFromAgent(nai, nc);
+        nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
+        processLinkPropertiesFromAgent(nai, lp);
+        nai.linkProperties = lp;
+
         nai.onNetworkMonitorCreated(networkMonitor);
-        if (VDBG) log("Got NetworkAgent Messenger");
+
         mNetworkAgentInfos.add(nai);
         synchronized (mNetworkForNetId) {
             mNetworkForNetId.put(nai.network.getNetId(), nai);
@@ -6963,10 +7178,11 @@
         } catch (RemoteException e) {
             e.rethrowAsRuntimeException();
         }
+
         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 {
@@ -7410,9 +7626,11 @@
      * Stores into |nai| any data coming from the agent that might also be written to the network's
      * NetworkCapabilities by ConnectivityService itself. This ensures that the data provided by the
      * agent is not lost when updateCapabilities is called.
-     * This method should never alter the agent's NetworkCapabilities, only store data in |nai|.
      */
     private void processCapabilitiesFromAgent(NetworkAgentInfo nai, NetworkCapabilities nc) {
+        if (nc.hasConnectivityManagedCapability()) {
+            Log.wtf(TAG, "BUG: " + nai + " has CS-managed capability.");
+        }
         // Note: resetting the owner UID before storing the agent capabilities in NAI means that if
         // the agent attempts to change the owner UID, then nai.declaredCapabilities will not
         // actually be the same as the capabilities sent by the agent. Still, it is safer to reset
@@ -7423,6 +7641,8 @@
             nc.setOwnerUid(nai.networkCapabilities.getOwnerUid());
         }
         nai.declaredCapabilities = new NetworkCapabilities(nc);
+        NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(nc, nai.creatorUid,
+                mCarrierPrivilegeAuthenticator);
     }
 
     /** Modifies |newNc| based on the capabilities of |underlyingNetworks| and |agentCaps|. */
@@ -7598,7 +7818,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)) {
@@ -7687,6 +7908,17 @@
         return stableRanges;
     }
 
+    private static UidRangeParcel[] intsToUidRangeStableParcels(
+            final @NonNull ArraySet<Integer> uids) {
+        final UidRangeParcel[] stableRanges = new UidRangeParcel[uids.size()];
+        int index = 0;
+        for (int uid : uids) {
+            stableRanges[index] = new UidRangeParcel(uid, uid);
+            index++;
+        }
+        return stableRanges;
+    }
+
     private static UidRangeParcel[] toUidRangeStableParcels(UidRange[] ranges) {
         final UidRangeParcel[] stableRanges = new UidRangeParcel[ranges.length];
         for (int i = 0; i < ranges.length; i++) {
@@ -7757,8 +7989,8 @@
         }
     }
 
-    private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc,
-            NetworkCapabilities newNc) {
+    private void updateVpnUids(@NonNull NetworkAgentInfo nai, @Nullable NetworkCapabilities prevNc,
+            @Nullable NetworkCapabilities newNc) {
         Set<UidRange> prevRanges = null == prevNc ? null : prevNc.getUidRanges();
         Set<UidRange> newRanges = null == newNc ? null : newNc.getUidRanges();
         if (null == prevRanges) prevRanges = new ArraySet<>();
@@ -7813,7 +8045,50 @@
             }
         } catch (Exception e) {
             // Never crash!
-            loge("Exception in updateUids: ", e);
+            loge("Exception in updateVpnUids: ", e);
+        }
+    }
+
+    private void updateAccessUids(@NonNull NetworkAgentInfo nai,
+            @Nullable NetworkCapabilities prevNc, @Nullable NetworkCapabilities newNc) {
+        // In almost all cases both NC code for empty access UIDs. return as fast as possible.
+        final boolean prevEmpty = null == prevNc || prevNc.getAccessUidsNoCopy().isEmpty();
+        final boolean newEmpty = null == newNc || newNc.getAccessUidsNoCopy().isEmpty();
+        if (prevEmpty && newEmpty) return;
+
+        final ArraySet<Integer> prevUids =
+                null == prevNc ? new ArraySet<>() : prevNc.getAccessUidsNoCopy();
+        final ArraySet<Integer> newUids =
+                null == newNc ? new ArraySet<>() : newNc.getAccessUidsNoCopy();
+
+        if (prevUids.equals(newUids)) return;
+
+        // This implementation is very simple and vastly faster for sets of Integers than
+        // CompareOrUpdateResult, which is tuned for sets that need to be compared based on
+        // a key computed from the value and has storage for that.
+        final ArraySet<Integer> toRemove = new ArraySet<>(prevUids);
+        final ArraySet<Integer> toAdd = new ArraySet<>(newUids);
+        toRemove.removeAll(newUids);
+        toAdd.removeAll(prevUids);
+
+        try {
+            if (!toAdd.isEmpty()) {
+                mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
+                        nai.network.netId,
+                        intsToUidRangeStableParcels(toAdd),
+                        PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT));
+            }
+            if (!toRemove.isEmpty()) {
+                mNetd.networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+                        nai.network.netId,
+                        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.
         }
     }
 
@@ -8717,6 +8992,19 @@
             // A network that has just connected has zero requests and is thus a foreground network.
             networkAgent.networkCapabilities.addCapability(NET_CAPABILITY_FOREGROUND);
 
+            // If a rate limit has been configured and is applicable to this network (network
+            // provides internet connectivity), apply it.
+            // Note: in case of a system server crash, there is a very small chance that this
+            // leaves some interfaces rate limited (i.e. if the rate limit had been changed just
+            // before the crash and was never applied). One solution would be to delete all
+            // potential tc police filters every time this is called. Since this is an unlikely
+            // scenario in the first place (and worst case, the interface stays rate limited until
+            // the device is rebooted), this seems a little overkill.
+            if (canNetworkBeRateLimited(networkAgent) && mIngressRateLimit >= 0) {
+                mDeps.enableIngressRateLimit(networkAgent.linkProperties.getInterfaceName(),
+                        mIngressRateLimit);
+            }
+
             if (!createNativeNetwork(networkAgent)) return;
             if (networkAgent.propagateUnderlyingCapabilities()) {
                 // Initialize the network's capabilities to their starting values according to the
@@ -8726,6 +9014,7 @@
             }
             networkAgent.created = true;
             networkAgent.onNetworkCreated();
+            updateAccessUids(networkAgent, null, networkAgent.networkCapabilities);
         }
 
         if (!networkAgent.everConnected && state == NetworkInfo.State.CONNECTED) {
@@ -8746,10 +9035,12 @@
             if (networkAgent.networkAgentConfig.acceptPartialConnectivity) {
                 networkAgent.networkMonitor().setAcceptPartialConnectivity();
             }
-            networkAgent.networkMonitor().notifyNetworkConnected(
-                    new LinkProperties(networkAgent.linkProperties,
-                            true /* parcelSensitiveFields */),
-                    networkAgent.networkCapabilities);
+            final NetworkMonitorParameters params = new NetworkMonitorParameters();
+            params.networkAgentConfig = networkAgent.networkAgentConfig;
+            params.networkCapabilities = networkAgent.networkCapabilities;
+            params.linkProperties = new LinkProperties(networkAgent.linkProperties,
+                    true /* parcelSensitiveFields */);
+            networkAgent.networkMonitor().notifyNetworkConnected(params);
             scheduleUnvalidatedPrompt(networkAgent);
 
             // Whether a particular NetworkRequest listen should cause signal strength thresholds to
@@ -8779,7 +9070,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()) {
@@ -9783,7 +10074,7 @@
                 android.Manifest.permission.NETWORK_STACK);
         final NetworkCapabilities nc = getNetworkCapabilitiesInternal(network);
         if (!nc.hasTransport(TRANSPORT_TEST)) {
-            throw new SecurityException("Data Stall simluation is only possible for test networks");
+            throw new SecurityException("Data Stall simulation is only possible for test networks");
         }
 
         final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
@@ -10374,6 +10665,39 @@
         rematchAllNetworksAndRequests();
     }
 
+    private void handleIngressRateLimitChanged() {
+        final long oldIngressRateLimit = mIngressRateLimit;
+        mIngressRateLimit = ConnectivitySettingsManager.getIngressRateLimitInBytesPerSecond(
+                mContext);
+        for (final NetworkAgentInfo networkAgent : mNetworkAgentInfos) {
+            if (canNetworkBeRateLimited(networkAgent)) {
+                // If rate limit has previously been enabled, remove the old limit first.
+                if (oldIngressRateLimit >= 0) {
+                    mDeps.disableIngressRateLimit(networkAgent.linkProperties.getInterfaceName());
+                }
+                if (mIngressRateLimit >= 0) {
+                    mDeps.enableIngressRateLimit(networkAgent.linkProperties.getInterfaceName(),
+                            mIngressRateLimit);
+                }
+            }
+        }
+    }
+
+    private boolean canNetworkBeRateLimited(@NonNull final NetworkAgentInfo networkAgent) {
+        if (!networkAgent.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) {
+            // rate limits only apply to networks that provide internet connectivity.
+            return false;
+        }
+
+        final String iface = networkAgent.linkProperties.getInterfaceName();
+        if (iface == null) {
+            // This can never happen.
+            logwtf("canNetworkBeRateLimited: LinkProperties#getInterfaceName returns null");
+            return false;
+        }
+        return true;
+    }
+
     private void enforceAutomotiveDevice() {
         PermissionUtils.enforceSystemFeature(mContext, PackageManager.FEATURE_AUTOMOTIVE,
                 "setOemNetworkPreference() is only available on automotive devices.");
@@ -10709,9 +11033,9 @@
 
         try {
             if (add) {
-                mNetd.bandwidthAddNiceApp(uid);
+                mBpfNetMaps.addNiceApp(uid);
             } else {
-                mNetd.bandwidthRemoveNiceApp(uid);
+                mBpfNetMaps.removeNiceApp(uid);
             }
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
@@ -10724,9 +11048,9 @@
 
         try {
             if (add) {
-                mNetd.bandwidthAddNaughtyApp(uid);
+                mBpfNetMaps.addNaughtyApp(uid);
             } else {
-                mNetd.bandwidthRemoveNaughtyApp(uid);
+                mBpfNetMaps.removeNaughtyApp(uid);
             }
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
@@ -10738,7 +11062,7 @@
         enforceNetworkStackOrSettingsPermission();
 
         try {
-            mNetd.firewallSetUidRule(chain, uid,
+            mBpfNetMaps.setUidRule(chain, uid,
                     allow ? INetd.FIREWALL_RULE_ALLOW : INetd.FIREWALL_RULE_DENY);
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
@@ -10750,7 +11074,7 @@
         enforceNetworkStackOrSettingsPermission();
 
         try {
-            mNetd.firewallEnableChildChain(chain, enable);
+            mBpfNetMaps.setChildChain(chain, enable);
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
@@ -10763,16 +11087,20 @@
         try {
             switch (chain) {
                 case ConnectivityManager.FIREWALL_CHAIN_DOZABLE:
-                    mNetd.firewallReplaceUidChain("fw_dozable", true /* isAllowList */, uids);
+                    mBpfNetMaps.replaceUidChain("fw_dozable", true /* isAllowList */, uids);
                     break;
                 case ConnectivityManager.FIREWALL_CHAIN_STANDBY:
-                    mNetd.firewallReplaceUidChain("fw_standby", false /* isAllowList */, uids);
+                    mBpfNetMaps.replaceUidChain("fw_standby", false /* isAllowList */, uids);
                     break;
                 case ConnectivityManager.FIREWALL_CHAIN_POWERSAVE:
-                    mNetd.firewallReplaceUidChain("fw_powersave", true /* isAllowList */, uids);
+                    mBpfNetMaps.replaceUidChain("fw_powersave", true /* isAllowList */, uids);
                     break;
                 case ConnectivityManager.FIREWALL_CHAIN_RESTRICTED:
-                    mNetd.firewallReplaceUidChain("fw_restricted", true /* isAllowList */, uids);
+                    mBpfNetMaps.replaceUidChain("fw_restricted", true /* isAllowList */, uids);
+                    break;
+                case ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY:
+                    mBpfNetMaps.replaceUidChain("fw_low_power_standby", true /* isAllowList */,
+                            uids);
                     break;
                 default:
                     throw new IllegalArgumentException("replaceFirewallChain with invalid chain: "
@@ -10787,7 +11115,7 @@
     public void swapActiveStatsMap() {
         enforceNetworkStackOrSettingsPermission();
         try {
-            mNetd.trafficSwapActiveStatsMap();
+            mBpfNetMaps.swapActiveStatsMap();
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
new file mode 100644
index 0000000..b761762
--- /dev/null
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static 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;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.NetworkCapabilities;
+import android.net.NetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.networkstack.apishim.TelephonyManagerShimImpl;
+import com.android.networkstack.apishim.common.TelephonyManagerShim;
+import com.android.networkstack.apishim.common.TelephonyManagerShim.CarrierPrivilegesListenerShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * Tracks the uid of the carrier privileged app that provides the carrier config.
+ * Authenticates if the caller has same uid as
+ * carrier privileged app that provides the carrier config
+ * @hide
+ */
+public class CarrierPrivilegeAuthenticator extends BroadcastReceiver {
+    private static final String TAG = CarrierPrivilegeAuthenticator.class.getSimpleName();
+    private static final boolean DBG = true;
+
+    // The context is for the current user (system server)
+    private final Context mContext;
+    private final TelephonyManagerShim mTelephonyManagerShim;
+    private final TelephonyManager mTelephonyManager;
+    @GuardedBy("mLock")
+    private int[] mCarrierServiceUid;
+    @GuardedBy("mLock")
+    private int mModemCount = 0;
+    private final Object mLock = new Object();
+    private final HandlerThread mThread;
+    private final Handler mHandler;
+    @NonNull
+    private final List<CarrierPrivilegesListenerShim> mCarrierPrivilegesChangedListeners =
+            new ArrayList<>();
+
+    public CarrierPrivilegeAuthenticator(@NonNull final Context c,
+            @NonNull final TelephonyManager t,
+            @NonNull final TelephonyManagerShimImpl telephonyManagerShim) {
+        mContext = c;
+        mTelephonyManager = t;
+        mTelephonyManagerShim = telephonyManagerShim;
+        mThread = new HandlerThread(TAG);
+        mThread.start();
+        mHandler = new Handler(mThread.getLooper()) {};
+        synchronized (mLock) {
+            mModemCount = mTelephonyManager.getActiveModemCount();
+            registerForCarrierChanges();
+            updateCarrierServiceUid();
+        }
+    }
+
+    public CarrierPrivilegeAuthenticator(@NonNull final Context c,
+            @NonNull final TelephonyManager t) {
+        mContext = c;
+        mTelephonyManager = t;
+        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S) {
+            mTelephonyManagerShim = new TelephonyManagerShimImpl(mTelephonyManager);
+        } else {
+            mTelephonyManagerShim = null;
+        }
+        mThread = new HandlerThread(TAG);
+        mThread.start();
+        mHandler = new Handler(mThread.getLooper()) {};
+        synchronized (mLock) {
+            mModemCount = mTelephonyManager.getActiveModemCount();
+            registerForCarrierChanges();
+            updateCarrierServiceUid();
+        }
+    }
+
+    /**
+     * An adapter {@link Executor} that posts all executed tasks onto the given
+     * {@link Handler}.
+     *
+     * TODO : migrate to the version in frameworks/libs/net when it's ready
+     *
+     * @hide
+     */
+    public class HandlerExecutor implements Executor {
+        private final Handler mHandler;
+        public HandlerExecutor(@NonNull Handler handler) {
+            mHandler = handler;
+        }
+        @Override
+        public void execute(Runnable command) {
+            if (!mHandler.post(command)) {
+                throw new RejectedExecutionException(mHandler + " is shutting down");
+            }
+        }
+    }
+
+    /**
+     * Broadcast receiver for ACTION_MULTI_SIM_CONFIG_CHANGED
+     *
+     * <p>The broadcast receiver is registered with mHandler
+     */
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        switch (intent.getAction()) {
+            case TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED:
+                handleActionMultiSimConfigChanged(context, intent);
+                break;
+            default:
+                Log.d(TAG, "Unknown intent received with action: " + intent.getAction());
+        }
+    }
+
+    private void handleActionMultiSimConfigChanged(Context context, Intent intent) {
+        unregisterCarrierPrivilegesListeners();
+        synchronized (mLock) {
+            mModemCount = mTelephonyManager.getActiveModemCount();
+        }
+        registerCarrierPrivilegesListeners();
+        updateCarrierServiceUid();
+    }
+
+    private void registerForCarrierChanges() {
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
+        mContext.registerReceiver(this, filter, null, mHandler, RECEIVER_NOT_EXPORTED /* flags */);
+        registerCarrierPrivilegesListeners();
+    }
+
+    private void registerCarrierPrivilegesListeners() {
+        final HandlerExecutor executor = new HandlerExecutor(mHandler);
+        int modemCount;
+        synchronized (mLock) {
+            modemCount = mModemCount;
+        }
+        try {
+            for (int i = 0; i < modemCount; i++) {
+                CarrierPrivilegesListenerShim carrierPrivilegesListener =
+                        new CarrierPrivilegesListenerShim() {
+                            @Override
+                            public void onCarrierPrivilegesChanged(
+                                    @NonNull List<String> privilegedPackageNames,
+                                    @NonNull int[] privilegedUids) {
+                                // Re-trigger the synchronous check (which is also very cheap due
+                                // to caching in CarrierPrivilegesTracker). This allows consistency
+                                // with the onSubscriptionsChangedListener and broadcasts.
+                                updateCarrierServiceUid();
+                            }
+                        };
+                addCarrierPrivilegesListener(i, executor, carrierPrivilegesListener);
+                mCarrierPrivilegesChangedListeners.add(carrierPrivilegesListener);
+            }
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Encountered exception registering carrier privileges listeners", e);
+        }
+    }
+
+    private void addCarrierPrivilegesListener(int logicalSlotIndex, Executor executor,
+            CarrierPrivilegesListenerShim listener) {
+        if (mTelephonyManagerShim  == null) {
+            return;
+        }
+        try {
+            mTelephonyManagerShim.addCarrierPrivilegesListener(
+                    logicalSlotIndex, executor, listener);
+        } catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+            Log.e(TAG, "addCarrierPrivilegesListener API is not available");
+        }
+    }
+
+    private void removeCarrierPrivilegesListener(CarrierPrivilegesListenerShim listener) {
+        if (mTelephonyManagerShim  == null) {
+            return;
+        }
+        try {
+            mTelephonyManagerShim.removeCarrierPrivilegesListener(listener);
+        } catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+            Log.e(TAG, "removeCarrierPrivilegesListener API is not available");
+        }
+    }
+
+    private String getCarrierServicePackageNameForLogicalSlot(int logicalSlotIndex) {
+        if (mTelephonyManagerShim  == null) {
+            return null;
+        }
+        try {
+            return mTelephonyManagerShim.getCarrierServicePackageNameForLogicalSlot(
+                    logicalSlotIndex);
+        } catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+            Log.e(TAG, "getCarrierServicePackageNameForLogicalSlot API is not available");
+        }
+        return null;
+    }
+
+    private void unregisterCarrierPrivilegesListeners() {
+        for (CarrierPrivilegesListenerShim carrierPrivilegesListener :
+                mCarrierPrivilegesChangedListeners) {
+            removeCarrierPrivilegesListener(carrierPrivilegesListener);
+        }
+        mCarrierPrivilegesChangedListeners.clear();
+    }
+
+    /**
+     * Check if a UID is the carrier service app of the subscription ID in the provided capabilities
+     *
+     * This returns whether the passed UID is the carrier service package for the subscription ID
+     * stored in the telephony network specifier in the passed network capabilities.
+     * If the capabilities don't code for a cellular network, or if they don't have the
+     * subscription ID in their specifier, this returns false.
+     *
+     * This method can be used to check that a network request for {@link NET_CAPABILITY_CBS} is
+     * allowed for the UID of a caller, which must hold carrier privilege and provide the carrier
+     * config.
+     * It can also be used to check that a factory is entitled to grant access to a given network
+     * to a given UID on grounds that it is the carrier service package.
+     *
+     * @param callingUid uid of the app claimed to be the carrier service package.
+     * @param networkCapabilities the network capabilities for which carrier privilege is checked.
+     * @return true if uid provides the relevant carrier config else false.
+     */
+    public boolean hasCarrierPrivilegeForNetworkCapabilities(int callingUid,
+            @NonNull NetworkCapabilities networkCapabilities) {
+        if (callingUid == Process.INVALID_UID) return false;
+        if (!networkCapabilities.hasSingleTransport(TRANSPORT_CELLULAR)) return false;
+        final int subId = getSubIdFromNetworkSpecifier(networkCapabilities.getNetworkSpecifier());
+        if (SubscriptionManager.INVALID_SUBSCRIPTION_ID == subId) return false;
+        return callingUid == getCarrierServiceUidForSubId(subId);
+    }
+
+    @VisibleForTesting
+    void updateCarrierServiceUid() {
+        synchronized (mLock) {
+            mCarrierServiceUid = new int[mModemCount];
+            for (int i = 0; i < mModemCount; i++) {
+                mCarrierServiceUid[i] = getCarrierServicePackageUidForSlot(i);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    int getCarrierServiceUidForSubId(int subId) {
+        final int slotId = getSlotIndex(subId);
+        synchronized (mLock) {
+            if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX && slotId < mModemCount) {
+                return mCarrierServiceUid[slotId];
+            }
+        }
+        return Process.INVALID_UID;
+    }
+
+    @VisibleForTesting
+    protected int getSlotIndex(int subId) {
+        return SubscriptionManager.getSlotIndex(subId);
+    }
+
+    @VisibleForTesting
+    int getSubIdFromNetworkSpecifier(NetworkSpecifier specifier) {
+        if (specifier instanceof TelephonyNetworkSpecifier) {
+            return ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
+        }
+        return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    }
+
+    @VisibleForTesting
+    int getUidForPackage(String pkgName) {
+        if (pkgName == null) {
+            return Process.INVALID_UID;
+        }
+        try {
+            PackageManager pm = mContext.getPackageManager();
+            if (pm != null) {
+                ApplicationInfo applicationInfo = pm.getApplicationInfo(pkgName, 0);
+                if (applicationInfo != null) {
+                    return applicationInfo.uid;
+                }
+            }
+        } catch (PackageManager.NameNotFoundException exception) {
+            // Didn't find package. Try other users
+            Log.i(TAG, "Unable to find uid for package " + pkgName);
+        }
+        return Process.INVALID_UID;
+    }
+
+    @VisibleForTesting
+    int getCarrierServicePackageUidForSlot(int slotId) {
+        return getUidForPackage(getCarrierServicePackageNameForLogicalSlot(slotId));
+    }
+}
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 1a7248a..e917b3f 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -17,7 +17,9 @@
 package com.android.server.connectivity;
 
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.transportNamesOf;
 
 import android.annotation.NonNull;
@@ -52,11 +54,13 @@
 import android.os.SystemClock;
 import android.telephony.data.EpsBearerQosSessionAttributes;
 import android.telephony.data.NrQosSessionAttributes;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
 
 import com.android.internal.util.WakeupMessage;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.ConnectivityService;
 
 import java.io.PrintWriter;
@@ -1188,6 +1192,54 @@
         return mConnectivityReport;
     }
 
+    /**
+     * Make sure the NC from network agents don't contain stuff they shouldn't.
+     *
+     * @param nc the capabilities to sanitize
+     * @param creatorUid the UID of the process creating this network agent
+     * @param authenticator the carrier privilege authenticator to check for telephony constraints
+     */
+    public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
+            final int creatorUid, @NonNull final CarrierPrivilegeAuthenticator authenticator) {
+        if (nc.hasTransport(TRANSPORT_TEST)) {
+            nc.restrictCapabilitiesForTestNetwork(creatorUid);
+        }
+        if (!areAccessUidsAcceptableFromNetworkAgent(nc, authenticator)) {
+            nc.setAccessUids(new ArraySet<>());
+        }
+    }
+
+    private static boolean areAccessUidsAcceptableFromNetworkAgent(
+            @NonNull final NetworkCapabilities nc,
+            @Nullable final CarrierPrivilegeAuthenticator carrierPrivilegeAuthenticator) {
+        // NCs without access UIDs are fine.
+        if (!nc.hasAccessUids()) return true;
+        // S and below must never accept access UIDs, even if an agent sends them, because netd
+        // didn't support the required feature in S.
+        if (!SdkLevel.isAtLeastT()) return false;
+
+        // On a non-restricted network, access UIDs make no sense
+        if (nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) return false;
+
+        // If this network has TRANSPORT_TEST, then the caller can do whatever they want to
+        // access UIDs
+        if (nc.hasTransport(TRANSPORT_TEST)) return true;
+
+        // Factories that make cell networks can allow the UID for the carrier service package.
+        // This can only work in T where there is support for CarrierPrivilegeAuthenticator
+        if (null != carrierPrivilegeAuthenticator
+                && nc.hasSingleTransport(TRANSPORT_CELLULAR)
+                && (1 == nc.getAccessUidsNoCopy().size())
+                && (carrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                        nc.getAccessUidsNoCopy().valueAt(0), nc))) {
+            return true;
+        }
+
+        // TODO : accept Railway callers
+
+        return false;
+    }
+
     // TODO: Print shorter members first and only print the boolean variable which value is true
     // to improve readability.
     public String toString() {
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index 439db89..ac46054 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -58,7 +58,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.system.OsConstants;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -68,6 +67,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.net.module.util.CollectionUtils;
+import com.android.server.BpfNetMaps;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -93,6 +93,7 @@
     private final INetd mNetd;
     private final Dependencies mDeps;
     private final Context mContext;
+    private final BpfNetMaps mBpfNetMaps;
 
     @GuardedBy("this")
     private final Set<UserHandle> mUsers = new HashSet<>();
@@ -184,12 +185,14 @@
         }
     }
 
-    public PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd) {
-        this(context, netd, new Dependencies());
+    public PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd,
+            @NonNull final BpfNetMaps bpfNetMaps) {
+        this(context, netd, bpfNetMaps, new Dependencies());
     }
 
     @VisibleForTesting
     PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd,
+            @NonNull final BpfNetMaps bpfNetMaps,
             @NonNull final Dependencies deps) {
         mPackageManager = context.getPackageManager();
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
@@ -197,6 +200,7 @@
         mNetd = netd;
         mDeps = deps;
         mContext = context;
+        mBpfNetMaps = bpfNetMaps;
     }
 
     private int getPackageNetdNetworkPermission(@NonNull final PackageInfo app) {
@@ -803,17 +807,11 @@
         }
         try {
             if (add) {
-                mNetd.firewallAddUidInterfaceRules(iface, toIntArray(uids));
+                mBpfNetMaps.addUidInterfaceRules(iface, toIntArray(uids));
             } else {
-                mNetd.firewallRemoveUidInterfaceRules(toIntArray(uids));
+                mBpfNetMaps.removeUidInterfaceRules(toIntArray(uids));
             }
-        } catch (ServiceSpecificException e) {
-            // Silently ignore exception when device does not support eBPF, otherwise just log
-            // the exception and do not crash
-            if (e.errorCode != OsConstants.EOPNOTSUPP) {
-                loge("Exception when updating permissions: ", e);
-            }
-        } catch (RemoteException e) {
+        } catch (RemoteException | ServiceSpecificException e) {
             loge("Exception when updating permissions: ", e);
         }
     }
@@ -878,26 +876,27 @@
         try {
             // TODO: add a lock inside netd to protect IPC trafficSetNetPermForUids()
             if (allPermissionAppIds.size() != 0) {
-                mNetd.trafficSetNetPermForUids(
+                mBpfNetMaps.setNetPermForUids(
                         PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS,
                         toIntArray(allPermissionAppIds));
             }
             if (internetPermissionAppIds.size() != 0) {
-                mNetd.trafficSetNetPermForUids(PERMISSION_INTERNET,
+                mBpfNetMaps.setNetPermForUids(PERMISSION_INTERNET,
                         toIntArray(internetPermissionAppIds));
             }
             if (updateStatsPermissionAppIds.size() != 0) {
-                mNetd.trafficSetNetPermForUids(PERMISSION_UPDATE_DEVICE_STATS,
+                mBpfNetMaps.setNetPermForUids(PERMISSION_UPDATE_DEVICE_STATS,
                         toIntArray(updateStatsPermissionAppIds));
             }
             if (noPermissionAppIds.size() != 0) {
-                mNetd.trafficSetNetPermForUids(PERMISSION_NONE, toIntArray(noPermissionAppIds));
+                mBpfNetMaps.setNetPermForUids(PERMISSION_NONE,
+                        toIntArray(noPermissionAppIds));
             }
             if (uninstalledAppIds.size() != 0) {
-                mNetd.trafficSetNetPermForUids(PERMISSION_UNINSTALLED,
+                mBpfNetMaps.setNetPermForUids(PERMISSION_UNINSTALLED,
                         toIntArray(uninstalledAppIds));
             }
-        } catch (RemoteException e) {
+        } catch (RemoteException | ServiceSpecificException e) {
             Log.e(TAG, "Pass appId list of special permission failed." + e);
         }
     }
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index acf04bf..8782684 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -88,6 +88,7 @@
         "libstaticjvmtiagent",
         // For NetworkStackUtils included in NetworkStackBase
         "libnetworkstackutilsjni",
+        "libandroid_net_connectivity_com_android_net_module_util_jni",
         "libcom_android_networkstack_tethering_util_jni",
         // For framework tests
         "libservice-connectivity",
diff --git a/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt b/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt
index ebaa787..8d8958d 100644
--- a/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt
+++ b/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt
@@ -39,6 +39,7 @@
 import android.net.ConnectivitySettingsManager.getDnsResolverSampleRanges
 import android.net.ConnectivitySettingsManager.getDnsResolverSampleValidityDuration
 import android.net.ConnectivitySettingsManager.getDnsResolverSuccessThresholdPercent
+import android.net.ConnectivitySettingsManager.getIngressRateLimitInBytesPerSecond
 import android.net.ConnectivitySettingsManager.getMobileDataActivityTimeout
 import android.net.ConnectivitySettingsManager.getMobileDataAlwaysOn
 import android.net.ConnectivitySettingsManager.getNetworkSwitchNotificationMaximumDailyCount
@@ -51,6 +52,7 @@
 import android.net.ConnectivitySettingsManager.setDnsResolverSampleRanges
 import android.net.ConnectivitySettingsManager.setDnsResolverSampleValidityDuration
 import android.net.ConnectivitySettingsManager.setDnsResolverSuccessThresholdPercent
+import android.net.ConnectivitySettingsManager.setIngressRateLimitInBytesPerSecond
 import android.net.ConnectivitySettingsManager.setMobileDataActivityTimeout
 import android.net.ConnectivitySettingsManager.setMobileDataAlwaysOn
 import android.net.ConnectivitySettingsManager.setNetworkSwitchNotificationMaximumDailyCount
@@ -292,4 +294,19 @@
                 setter = { setWifiAlwaysRequested(context, it) },
                 testIntValues = intArrayOf(0))
     }
+
+    @Test
+    fun testInternetNetworkRateLimitInBytesPerSecond() {
+        val defaultRate = getIngressRateLimitInBytesPerSecond(context)
+        val testRate = 1000L
+        setIngressRateLimitInBytesPerSecond(context, testRate)
+        assertEquals(testRate, getIngressRateLimitInBytesPerSecond(context))
+
+        setIngressRateLimitInBytesPerSecond(context, defaultRate)
+        assertEquals(defaultRate, getIngressRateLimitInBytesPerSecond(context))
+
+        assertFailsWith<IllegalArgumentException>("Expected failure, but setting accepted") {
+            setIngressRateLimitInBytesPerSecond(context, -10)
+        }
+    }
 }
\ No newline at end of file
diff --git a/tests/common/java/android/net/NetworkAgentConfigTest.kt b/tests/common/java/android/net/NetworkAgentConfigTest.kt
index b339a27..e5db09f 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()) {
+                setLocalRoutesExcludedForVpn(true)
+                setVpnRequiresValidation(true)
+            }
         }.build()
         assertParcelingIsLossless(config)
     }
@@ -69,6 +74,10 @@
                 setProvisioningNotificationEnabled(false)
                 setBypassableVpn(true)
             }
+            if (isAtLeastT()) {
+                setLocalRoutesExcludedForVpn(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.areLocalRoutesExcludedForVpn())
+            assertTrue(config.getVpnRequiresValidation())
+        }
         if (isAtLeastS()) {
             assertEquals(testExtraInfo, config.getLegacyExtraInfo())
             assertFalse(config.isNat64DetectionEnabled())
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index bea00a9..b6926a8 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -49,6 +49,7 @@
 import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
 import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_USB;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
@@ -308,6 +309,48 @@
         }
     }
 
+    @Test @IgnoreUpTo(SC_V2)
+    public void testSetAccessUids() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        assertThrows(NullPointerException.class, () -> nc.setAccessUids(null));
+        assertFalse(nc.hasAccessUids());
+        assertFalse(nc.isAccessUid(0));
+        assertFalse(nc.isAccessUid(1000));
+        assertEquals(0, nc.getAccessUids().size());
+        nc.setAccessUids(new ArraySet<>());
+        assertFalse(nc.hasAccessUids());
+        assertFalse(nc.isAccessUid(0));
+        assertFalse(nc.isAccessUid(1000));
+        assertEquals(0, nc.getAccessUids().size());
+
+        final ArraySet<Integer> uids = new ArraySet<>();
+        uids.add(200);
+        uids.add(250);
+        uids.add(-1);
+        uids.add(Integer.MAX_VALUE);
+        nc.setAccessUids(uids);
+        assertNotEquals(nc, new NetworkCapabilities());
+        assertTrue(nc.hasAccessUids());
+
+        final List<Integer> includedList = List.of(-2, 0, 199, 700, 901, 1000, Integer.MIN_VALUE);
+        final List<Integer> excludedList = List.of(-1, 200, 250, Integer.MAX_VALUE);
+        for (final int uid : includedList) {
+            assertFalse(nc.isAccessUid(uid));
+        }
+        for (final int uid : excludedList) {
+            assertTrue(nc.isAccessUid(uid));
+        }
+
+        final Set<Integer> outUids = nc.getAccessUids();
+        assertEquals(4, outUids.size());
+        for (final int uid : includedList) {
+            assertFalse(outUids.contains(uid));
+        }
+        for (final int uid : excludedList) {
+            assertTrue(outUids.contains(uid));
+        }
+    }
+
     @Test
     public void testParcelNetworkCapabilities() {
         final Set<Range<Integer>> uids = new ArraySet<>();
@@ -318,6 +361,10 @@
             .addCapability(NET_CAPABILITY_EIMS)
             .addCapability(NET_CAPABILITY_NOT_METERED);
         if (isAtLeastS()) {
+            final ArraySet<Integer> accessUids = new ArraySet<>();
+            accessUids.add(4);
+            accessUids.add(9);
+            netCap.setAccessUids(accessUids);
             netCap.setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2));
             netCap.setUids(uids);
         }
@@ -681,25 +728,38 @@
     @Test
     public void testSetNetworkSpecifierOnMultiTransportNc() {
         // Sequence 1: Transport + Transport + NetworkSpecifier
-        NetworkCapabilities nc1 = new NetworkCapabilities();
+        NetworkCapabilities.Builder nc1 = new NetworkCapabilities.Builder();
         nc1.addTransportType(TRANSPORT_CELLULAR).addTransportType(TRANSPORT_WIFI);
-        try {
-            nc1.setNetworkSpecifier(CompatUtil.makeEthernetNetworkSpecifier("eth0"));
-            fail("Cannot set NetworkSpecifier on a NetworkCapability with multiple transports!");
-        } catch (IllegalStateException expected) {
-            // empty
-        }
+        final NetworkSpecifier specifier = CompatUtil.makeEthernetNetworkSpecifier("eth0");
+        assertThrows("Cannot set NetworkSpecifier on a NetworkCapability with multiple transports!",
+                IllegalStateException.class,
+                () -> nc1.build().setNetworkSpecifier(specifier));
+        assertThrows("Cannot set NetworkSpecifier on a NetworkCapability with multiple transports!",
+                IllegalStateException.class,
+                () -> nc1.setNetworkSpecifier(specifier));
 
         // Sequence 2: Transport + NetworkSpecifier + Transport
-        NetworkCapabilities nc2 = new NetworkCapabilities();
-        nc2.addTransportType(TRANSPORT_CELLULAR).setNetworkSpecifier(
-                CompatUtil.makeEthernetNetworkSpecifier("testtap3"));
-        try {
-            nc2.addTransportType(TRANSPORT_WIFI);
-            fail("Cannot set a second TransportType of a network which has a NetworkSpecifier!");
-        } catch (IllegalStateException expected) {
-            // empty
-        }
+        NetworkCapabilities.Builder nc2 = new NetworkCapabilities.Builder();
+        nc2.addTransportType(TRANSPORT_CELLULAR).setNetworkSpecifier(specifier);
+
+        assertThrows("Cannot set a second TransportType of a network which has a NetworkSpecifier!",
+                IllegalStateException.class,
+                () -> nc2.build().addTransportType(TRANSPORT_WIFI));
+        assertThrows("Cannot set a second TransportType of a network which has a NetworkSpecifier!",
+                IllegalStateException.class,
+                () -> nc2.addTransportType(TRANSPORT_WIFI));
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R) // New behavior in updatable NetworkCapabilities (S+)
+    public void testSetNetworkSpecifierOnTestMultiTransportNc() {
+        final NetworkSpecifier specifier = CompatUtil.makeEthernetNetworkSpecifier("eth0");
+        NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_TEST)
+                .addTransportType(TRANSPORT_ETHERNET)
+                .setNetworkSpecifier(specifier)
+                .build();
+        // Adding a specifier did not crash with 2 transports if one is TEST
+        assertEquals(specifier, nc.getNetworkSpecifier());
     }
 
     @Test
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/Android.bp b/tests/cts/net/Android.bp
index f66231d..e979a3b 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -70,7 +70,7 @@
 // devices.
 android_test {
     name: "CtsNetTestCases",
-    defaults: ["CtsNetTestCasesDefaults", "NetworkStackNextEnableDefaults"],
+    defaults: ["CtsNetTestCasesDefaults", "ConnectivityNextEnableDefaults"],
     // TODO: CTS should not depend on the entirety of the networkstack code.
     static_libs: [
         "NetworkStackApiCurrentLib",
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/ConnectivityFrameworkInitializerTiramisuTest.kt b/tests/cts/net/src/android/net/cts/ConnectivityFrameworkInitializerTiramisuTest.kt
new file mode 100644
index 0000000..049372f
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/ConnectivityFrameworkInitializerTiramisuTest.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts
+
+import android.net.nsd.NsdManager
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.networkstack.apishim.ConnectivityFrameworkInitShimImpl
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.SC_V2
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertNotNull
+
+private val cfiShim = ConnectivityFrameworkInitShimImpl.newInstance()
+
+@RunWith(DevSdkIgnoreRunner::class)
+// ConnectivityFrameworkInitializerTiramisu was added in T
+@IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+class ConnectivityFrameworkInitializerTiramisuTest {
+    @Test
+    fun testServicesRegistered() {
+        val ctx = InstrumentationRegistry.getInstrumentation().context as android.content.Context
+        assertNotNull(ctx.getSystemService(NsdManager::class.java),
+                "NsdManager not registered")
+    }
+
+    // registerServiceWrappers can only be called during initialization and should throw otherwise
+    @Test(expected = IllegalStateException::class)
+    fun testThrows() {
+        cfiShim.registerServiceWrappers()
+    }
+}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 53e4ab7..ea64252 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -16,9 +16,15 @@
 
 package android.net.cts;
 
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.ACCESS_NETWORK_STATE;
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
+import static android.Manifest.permission.NETWORK_FACTORY;
 import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.NETWORK_SETUP_WIZARD;
+import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.READ_DEVICE_CONFIG;
 import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
 import static android.content.pm.PackageManager.FEATURE_ETHERNET;
@@ -54,7 +60,9 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.net.cts.util.CtsNetUtils.ConnectivityActionReceiver;
 import static android.net.cts.util.CtsNetUtils.HTTP_PORT;
 import static android.net.cts.util.CtsNetUtils.NETWORK_CALLBACK_ACTION;
@@ -64,6 +72,7 @@
 import static android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL;
 import static android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL;
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+import static android.os.Process.INVALID_UID;
 import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
 import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.AF_INET6;
@@ -76,6 +85,7 @@
 import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_LOCKDOWN_VPN;
 import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_NONE;
 import static com.android.testutils.Cleanup.testAndCleanup;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 import static com.android.testutils.MiscAsserts.assertThrows;
 import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
 import static com.android.testutils.TestPermissionUtil.runAsShell;
@@ -103,6 +113,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.net.CaptivePortalData;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.ConnectivitySettingsManager;
@@ -132,6 +143,7 @@
 import android.net.cts.util.CtsNetUtils;
 import android.net.cts.util.CtsTetheringUtils;
 import android.net.util.KeepaliveUtils;
+import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.os.Binder;
 import android.os.Build;
@@ -160,6 +172,7 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.CollectionUtils;
 import com.android.networkstack.apishim.ConnectivityManagerShimImpl;
 import com.android.networkstack.apishim.ConstantsShim;
 import com.android.networkstack.apishim.NetworkInformationShimImpl;
@@ -554,6 +567,223 @@
         }
     }
 
+    private boolean checkPermission(String perm, int uid) {
+        return mContext.checkPermission(perm, -1 /* pid */, uid) == PERMISSION_GRANTED;
+    }
+
+    private String findPackageByPermissions(@NonNull List<String> requiredPermissions,
+                @NonNull List<String> forbiddenPermissions) throws Exception {
+        final List<PackageInfo> packageInfos =
+                mPackageManager.getInstalledPackages(GET_PERMISSIONS);
+        for (PackageInfo packageInfo : packageInfos) {
+            final int uid = mPackageManager.getPackageUid(packageInfo.packageName, 0 /* flags */);
+            if (!CollectionUtils.all(requiredPermissions, perm -> checkPermission(perm, uid))) {
+                continue;
+            }
+            if (CollectionUtils.any(forbiddenPermissions, perm -> checkPermission(perm, uid))) {
+                continue;
+            }
+
+            return packageInfo.packageName;
+        }
+        return null;
+    }
+
+    @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+    @Test
+    public void testRedactLinkPropertiesForPackage() throws Exception {
+        final String groundedPkg = findPackageByPermissions(
+                List.of(), /* requiredPermissions */
+                List.of(ACCESS_NETWORK_STATE) /* forbiddenPermissions */);
+        assertNotNull("Couldn't find any package without ACCESS_NETWORK_STATE", groundedPkg);
+        final int groundedUid = mPackageManager.getPackageUid(groundedPkg, 0 /* flags */);
+
+        final String normalPkg = findPackageByPermissions(
+                List.of(ACCESS_NETWORK_STATE) /* requiredPermissions */,
+                List.of(NETWORK_SETTINGS, NETWORK_STACK,
+                        PERMISSION_MAINLINE_NETWORK_STACK) /* forbiddenPermissions */);
+        assertNotNull("Couldn't find any package with ACCESS_NETWORK_STATE but"
+                + " without NETWORK_SETTINGS", normalPkg);
+        final int normalUid = mPackageManager.getPackageUid(normalPkg, 0 /* flags */);
+
+        // There are some privileged packages on the system, like the phone process, the network
+        // stack and the system server.
+        final String privilegedPkg = findPackageByPermissions(
+                List.of(ACCESS_NETWORK_STATE, NETWORK_SETTINGS), /* requiredPermissions */
+                List.of() /* forbiddenPermissions */);
+        assertNotNull("Couldn't find a package with sufficient permissions", privilegedPkg);
+        final int privilegedUid = mPackageManager.getPackageUid(privilegedPkg, 0);
+
+        // Set parcelSensitiveFields to true to preserve CaptivePortalApiUrl & CaptivePortalData
+        // when parceling.
+        final LinkProperties lp = new LinkProperties(new LinkProperties(),
+                true /* parcelSensitiveFields */);
+        final Uri capportUrl = Uri.parse("https://capport.example.com/api");
+        final CaptivePortalData capportData = new CaptivePortalData.Builder().build();
+        final int mtu = 12345;
+        lp.setMtu(mtu);
+        lp.setCaptivePortalApiUrl(capportUrl);
+        lp.setCaptivePortalData(capportData);
+
+        // No matter what the given uid is, a SecurityException will be thrown if the caller
+        // doesn't hold the NETWORK_SETTINGS permission.
+        assertThrows(SecurityException.class,
+                () -> mCm.redactLinkPropertiesForPackage(lp, groundedUid, groundedPkg));
+        assertThrows(SecurityException.class,
+                () -> mCm.redactLinkPropertiesForPackage(lp, normalUid, normalPkg));
+        assertThrows(SecurityException.class,
+                () -> mCm.redactLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg));
+
+        runAsShell(NETWORK_SETTINGS, () -> {
+            // No matter what the given uid is, if the given LinkProperties is null, then
+            // NullPointerException will be thrown.
+            assertThrows(NullPointerException.class,
+                    () -> mCm.redactLinkPropertiesForPackage(null, groundedUid, groundedPkg));
+            assertThrows(NullPointerException.class,
+                    () -> mCm.redactLinkPropertiesForPackage(null, normalUid, normalPkg));
+            assertThrows(NullPointerException.class,
+                    () -> mCm.redactLinkPropertiesForPackage(null, privilegedUid, privilegedPkg));
+
+            // Make sure null is returned for a UID without ACCESS_NETWORK_STATE.
+            assertNull(mCm.redactLinkPropertiesForPackage(lp, groundedUid, groundedPkg));
+
+            // CaptivePortalApiUrl & CaptivePortalData will be set to null if given uid doesn't hold
+            // the NETWORK_SETTINGS permission.
+            assertNull(mCm.redactLinkPropertiesForPackage(lp, normalUid, normalPkg)
+                    .getCaptivePortalApiUrl());
+            assertNull(mCm.redactLinkPropertiesForPackage(lp, normalUid, normalPkg)
+                    .getCaptivePortalData());
+            // MTU is not sensitive and is not redacted.
+            assertEquals(mtu, mCm.redactLinkPropertiesForPackage(lp, normalUid, normalPkg)
+                    .getMtu());
+
+            // CaptivePortalApiUrl & CaptivePortalData will be preserved if the given uid holds the
+            // NETWORK_SETTINGS permission.
+            assertEquals(capportUrl,
+                    mCm.redactLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg)
+                            .getCaptivePortalApiUrl());
+            assertEquals(capportData,
+                    mCm.redactLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg)
+                            .getCaptivePortalData());
+        });
+    }
+
+    private NetworkCapabilities redactNc(@NonNull final NetworkCapabilities nc, int uid,
+            @NonNull String packageName) {
+        return mCm.redactNetworkCapabilitiesForPackage(nc, uid, packageName);
+    }
+
+    @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+    @Test
+    public void testRedactNetworkCapabilitiesForPackage() throws Exception {
+        final String groundedPkg = findPackageByPermissions(
+                List.of(), /* requiredPermissions */
+                List.of(ACCESS_NETWORK_STATE) /* forbiddenPermissions */);
+        assertNotNull("Couldn't find any package without ACCESS_NETWORK_STATE", groundedPkg);
+        final int groundedUid = mPackageManager.getPackageUid(groundedPkg, 0 /* flags */);
+
+        // A package which doesn't have any of the permissions below, but has NETWORK_STATE.
+        // There should be a number of packages like this on the device; AOSP has many,
+        // including contacts, webview, the keyboard, pacprocessor, messaging.
+        final String normalPkg = findPackageByPermissions(
+                List.of(ACCESS_NETWORK_STATE) /* requiredPermissions */,
+                List.of(NETWORK_SETTINGS, NETWORK_FACTORY, NETWORK_SETUP_WIZARD,
+                        NETWORK_STACK, PERMISSION_MAINLINE_NETWORK_STACK,
+                        ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION) /* forbiddenPermissions */);
+        assertNotNull("Can't find a package with ACCESS_NETWORK_STATE but without any of"
+                + " the forbidden permissions", normalPkg);
+        final int normalUid = mPackageManager.getPackageUid(normalPkg, 0 /* flags */);
+
+        // There are some privileged packages on the system, like the phone process, the network
+        // stack and the system server.
+        final String privilegedPkg = findPackageByPermissions(
+                List.of(ACCESS_NETWORK_STATE, NETWORK_SETTINGS, NETWORK_FACTORY,
+                        ACCESS_FINE_LOCATION), /* requiredPermissions */
+                List.of() /* forbiddenPermissions */);
+        assertNotNull("Couldn't find a package with sufficient permissions", privilegedPkg);
+        final int privilegedUid = mPackageManager.getPackageUid(privilegedPkg, 0);
+
+        final Set<Range<Integer>> uids = new ArraySet<>();
+        uids.add(new Range<>(10000, 10100));
+        uids.add(new Range<>(10200, 10300));
+        final String ssid = "My-WiFi";
+        // This test will set underlying networks in the capabilities to redact to see if they
+        // are appropriately redacted, so fetch the default network to put in there as an example.
+        final Network defaultNetwork = mCm.getActiveNetwork();
+        assertNotNull("CTS requires a working Internet connection", defaultNetwork);
+        final int subId1 = 1;
+        final int subId2 = 2;
+        final int[] administratorUids = {normalUid};
+        final String bssid = "location sensitive";
+        final int rssi = 43; // not location sensitive
+        final WifiInfo wifiInfo = new WifiInfo.Builder()
+                .setBssid(bssid)
+                .setRssi(rssi)
+                .build();
+        final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .setUids(uids)
+                .setSsid(ssid)
+                .setUnderlyingNetworks(List.of(defaultNetwork))
+                .setSubscriptionIds(Set.of(subId1, subId2))
+                .setAdministratorUids(administratorUids)
+                .setOwnerUid(normalUid)
+                .setTransportInfo(wifiInfo)
+                .build();
+
+        // No matter what the given uid is, a SecurityException will be thrown if the caller
+        // doesn't hold the NETWORK_SETTINGS permission.
+        assertThrows(SecurityException.class, () -> redactNc(nc, groundedUid, groundedPkg));
+        assertThrows(SecurityException.class, () -> redactNc(nc, normalUid, normalPkg));
+        assertThrows(SecurityException.class, () -> redactNc(nc, privilegedUid, privilegedPkg));
+
+        runAsShell(NETWORK_SETTINGS, () -> {
+            // Make sure that the NC is null if the package doesn't hold ACCESS_NETWORK_STATE.
+            assertNull(redactNc(nc, groundedUid, groundedPkg));
+
+            // Uids, ssid, underlying networks & subscriptionIds will be redacted if the given uid
+            // doesn't hold the associated permissions. The wifi transport info is also suitably
+            // redacted.
+            final NetworkCapabilities redactedNormal = redactNc(nc, normalUid, normalPkg);
+            assertNull(redactedNormal.getUids());
+            assertNull(redactedNormal.getSsid());
+            assertNull(redactedNormal.getUnderlyingNetworks());
+            assertEquals(0, redactedNormal.getSubscriptionIds().size());
+            assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS,
+                    ((WifiInfo) redactedNormal.getTransportInfo()).getBSSID());
+            assertEquals(rssi, ((WifiInfo) redactedNormal.getTransportInfo()).getRssi());
+
+            // Uids, ssid, underlying networks & subscriptionIds will be preserved if the given uid
+            // holds the associated permissions.
+            final NetworkCapabilities redactedPrivileged =
+                    redactNc(nc, privilegedUid, privilegedPkg);
+            assertEquals(uids, redactedPrivileged.getUids());
+            assertEquals(ssid, redactedPrivileged.getSsid());
+            assertEquals(List.of(defaultNetwork), redactedPrivileged.getUnderlyingNetworks());
+            assertEquals(Set.of(subId1, subId2), redactedPrivileged.getSubscriptionIds());
+            assertEquals(bssid, ((WifiInfo) redactedPrivileged.getTransportInfo()).getBSSID());
+            assertEquals(rssi, ((WifiInfo) redactedPrivileged.getTransportInfo()).getRssi());
+
+            // The owner uid is only preserved when the network is a VPN and the uid is the
+            // same as the owner uid.
+            nc.addTransportType(TRANSPORT_VPN);
+            assertEquals(normalUid, redactNc(nc, normalUid, normalPkg).getOwnerUid());
+            assertEquals(INVALID_UID, redactNc(nc, privilegedUid, privilegedPkg).getOwnerUid());
+            nc.removeTransportType(TRANSPORT_VPN);
+
+            // If the given uid doesn't hold location permissions, the owner uid will be set to
+            // INVALID_UID even when sent to that UID (this avoids a wifi suggestor knowing where
+            // the device is by virtue of the device connecting to its own network).
+            assertEquals(INVALID_UID, redactNc(nc, normalUid, normalPkg).getOwnerUid());
+
+            // If the given uid holds location permissions, the owner uid is preserved. This works
+            // because the shell holds ACCESS_FINE_LOCATION.
+            final int[] administratorUids2 = { privilegedUid };
+            nc.setAdministratorUids(administratorUids2);
+            nc.setOwnerUid(privilegedUid);
+            assertEquals(privilegedUid, redactNc(nc, privilegedUid, privilegedPkg).getOwnerUid());
+        });
+    }
+
     /**
      * Tests that connections can be opened on WiFi and cellphone networks,
      * and that they are made from different IP addresses.
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index 1f39fee..886b078 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -146,8 +146,9 @@
     fun tearDown() {
         agentsToCleanUp.forEach { it.unregister() }
         callbacksToCleanUp.forEach { cm.unregisterNetworkCallback(it) }
+
+        // reader.stop() cleans up tun fd
         reader.handler.post { reader.stop() }
-        Os.close(iface.fileDescriptor.fileDescriptor)
         handlerThread.quitSafely()
     }
 
@@ -519,4 +520,4 @@
 
 private fun <T> Context.assertHasService(manager: Class<T>): T {
     return getSystemService(manager) ?: fail("Service $manager not found")
-}
\ No newline at end of file
+}
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/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index ef5dc77..344482b 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -23,7 +23,6 @@
 import android.net.INetworkAgentRegistry
 import android.net.InetAddresses
 import android.net.IpPrefix
-import android.net.KeepalivePacketData
 import android.net.LinkAddress
 import android.net.LinkProperties
 import android.net.NattKeepalivePacketData
@@ -42,6 +41,7 @@
 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
 import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
 import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
 import android.net.NetworkCapabilities.TRANSPORT_TEST
 import android.net.NetworkCapabilities.TRANSPORT_VPN
 import android.net.NetworkInfo
@@ -53,7 +53,6 @@
 import android.net.QosCallback
 import android.net.QosCallbackException
 import android.net.QosCallback.QosCallbackRegistrationException
-import android.net.QosFilter
 import android.net.QosSession
 import android.net.QosSessionAttributes
 import android.net.QosSocketInfo
@@ -67,7 +66,6 @@
 import android.os.Build
 import android.os.Handler
 import android.os.HandlerThread
-import android.os.Looper
 import android.os.Message
 import android.os.SystemClock
 import android.telephony.TelephonyManager
@@ -82,6 +80,8 @@
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
 import com.android.testutils.RecorderCallback.CallbackEntry.Losing
 import com.android.testutils.RecorderCallback.CallbackEntry.Lost
 import com.android.testutils.TestableNetworkAgent
@@ -100,7 +100,6 @@
 import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus
 import com.android.testutils.TestableNetworkCallback
 import org.junit.After
-import org.junit.Assert.assertArrayEquals
 import org.junit.Assume.assumeFalse
 import org.junit.Before
 import org.junit.Test
@@ -462,6 +461,36 @@
         }
     }
 
+    private fun ncWithAccessUids(vararg uids: Int) = NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_TEST)
+                .setAccessUids(uids.toSet()).build()
+
+    @Test
+    fun testRejectedUpdates() {
+        val callback = TestableNetworkCallback()
+        // will be cleaned up in tearDown
+        registerNetworkCallback(makeTestNetworkRequest(), callback)
+        val agent = createNetworkAgent(initialNc = ncWithAccessUids(200))
+        agent.register()
+        agent.markConnected()
+
+        // Make sure the UIDs have been ignored.
+        callback.expectCallback<Available>(agent.network!!)
+        callback.expectCapabilitiesThat(agent.network!!) {
+            it.accessUids.isEmpty() && !it.hasCapability(NET_CAPABILITY_VALIDATED)
+        }
+        callback.expectCallback<LinkPropertiesChanged>(agent.network!!)
+        callback.expectCallback<BlockedStatus>(agent.network!!)
+        callback.expectCapabilitiesThat(agent.network!!) {
+            it.accessUids.isEmpty() && it.hasCapability(NET_CAPABILITY_VALIDATED)
+        }
+        callback.assertNoCallback(NO_CALLBACK_TIMEOUT)
+
+        // Make sure that the UIDs are also ignored upon update
+        agent.sendNetworkCapabilities(ncWithAccessUids(200, 300))
+        callback.assertNoCallback(NO_CALLBACK_TIMEOUT)
+    }
+
     @Test
     fun testSendScore() {
         // This test will create two networks and check that the one with the stronger
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
new file mode 100644
index 0000000..147fca9
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -0,0 +1,878 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.cts;
+
+import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_ALL;
+import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_NO;
+import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_YES;
+import static android.app.usage.NetworkStats.Bucket.METERED_ALL;
+import static android.app.usage.NetworkStats.Bucket.METERED_NO;
+import static android.app.usage.NetworkStats.Bucket.METERED_YES;
+import static android.app.usage.NetworkStats.Bucket.STATE_ALL;
+import static android.app.usage.NetworkStats.Bucket.STATE_DEFAULT;
+import static android.app.usage.NetworkStats.Bucket.STATE_FOREGROUND;
+import static android.app.usage.NetworkStats.Bucket.TAG_NONE;
+import static android.app.usage.NetworkStats.Bucket.UID_ALL;
+
+import android.app.AppOpsManager;
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.net.TrafficStats;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.telephony.TelephonyManager;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+
+public class NetworkStatsManagerTest extends InstrumentationTestCase {
+    private static final String LOG_TAG = "NetworkStatsManagerTest";
+    private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} {1} {2}";
+    private static final String APPOPS_GET_SHELL_COMMAND = "appops get {0} {1}";
+
+    private static final long MINUTE = 1000 * 60;
+    private static final int TIMEOUT_MILLIS = 15000;
+
+    private static final String CHECK_CONNECTIVITY_URL = "http://www.265.com/";
+    private static final int HOST_RESOLUTION_RETRIES = 4;
+    private static final int HOST_RESOLUTION_INTERVAL_MS = 500;
+
+    private static final int NETWORK_TAG = 0xf00d;
+    private static final long THRESHOLD_BYTES = 2 * 1024 * 1024;  // 2 MB
+
+    private abstract class NetworkInterfaceToTest {
+        private boolean mMetered;
+        private boolean mIsDefault;
+
+        abstract int getNetworkType();
+        abstract int getTransportType();
+
+        public boolean getMetered() {
+            return mMetered;
+        }
+
+        public void setMetered(boolean metered) {
+            this.mMetered = metered;
+        }
+
+        public boolean getIsDefault() {
+            return mIsDefault;
+        }
+
+        public void setIsDefault(boolean isDefault) {
+            mIsDefault = isDefault;
+        }
+
+        abstract String getSystemFeature();
+        abstract String getErrorMessage();
+    }
+
+    private final NetworkInterfaceToTest[] mNetworkInterfacesToTest =
+            new NetworkInterfaceToTest[] {
+                    new NetworkInterfaceToTest() {
+                        @Override
+                        public int getNetworkType() {
+                            return ConnectivityManager.TYPE_WIFI;
+                        }
+
+                        @Override
+                        public int getTransportType() {
+                            return NetworkCapabilities.TRANSPORT_WIFI;
+                        }
+
+                        @Override
+                        public String getSystemFeature() {
+                            return PackageManager.FEATURE_WIFI;
+                        }
+
+                        @Override
+                        public String getErrorMessage() {
+                            return " Please make sure you are connected to a WiFi access point.";
+                        }
+                    },
+                    new NetworkInterfaceToTest() {
+                        @Override
+                        public int getNetworkType() {
+                            return ConnectivityManager.TYPE_MOBILE;
+                        }
+
+                        @Override
+                        public int getTransportType() {
+                            return NetworkCapabilities.TRANSPORT_CELLULAR;
+                        }
+
+                        @Override
+                        public String getSystemFeature() {
+                            return PackageManager.FEATURE_TELEPHONY;
+                        }
+
+                        @Override
+                        public String getErrorMessage() {
+                            return " Please make sure you have added a SIM card with data plan to"
+                                    + " your phone, have enabled data over cellular and in case of"
+                                    + " dual SIM devices, have selected the right SIM "
+                                    + "for data connection.";
+                        }
+                    }
+            };
+
+    private String mPkg;
+    private NetworkStatsManager mNsm;
+    private ConnectivityManager mCm;
+    private PackageManager mPm;
+    private long mStartTime;
+    private long mEndTime;
+
+    private long mBytesRead;
+    private String mWriteSettingsMode;
+    private String mUsageStatsMode;
+
+    private void exerciseRemoteHost(Network network, URL url) throws Exception {
+        NetworkInfo networkInfo = mCm.getNetworkInfo(network);
+        if (networkInfo == null) {
+            Log.w(LOG_TAG, "Network info is null");
+        } else {
+            Log.w(LOG_TAG, "Network: " + networkInfo.toString());
+        }
+        InputStreamReader in = null;
+        HttpURLConnection urlc = null;
+        String originalKeepAlive = System.getProperty("http.keepAlive");
+        System.setProperty("http.keepAlive", "false");
+        try {
+            TrafficStats.setThreadStatsTag(NETWORK_TAG);
+            urlc = (HttpURLConnection) network.openConnection(url);
+            urlc.setConnectTimeout(TIMEOUT_MILLIS);
+            urlc.setUseCaches(false);
+            // Disable compression so we generate enough traffic that assertWithinPercentage will
+            // not be affected by the small amount of traffic (5-10kB) sent by the test harness.
+            urlc.setRequestProperty("Accept-Encoding", "identity");
+            urlc.connect();
+            boolean ping = urlc.getResponseCode() == 200;
+            if (ping) {
+                in = new InputStreamReader(
+                        (InputStream) urlc.getContent());
+
+                mBytesRead = 0;
+                while (in.read() != -1) ++mBytesRead;
+            }
+        } catch (Exception e) {
+            Log.i(LOG_TAG, "Badness during exercising remote server: " + e);
+        } finally {
+            TrafficStats.clearThreadStatsTag();
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    // don't care
+                }
+            }
+            if (urlc != null) {
+                urlc.disconnect();
+            }
+            if (originalKeepAlive == null) {
+                System.clearProperty("http.keepAlive");
+            } else {
+                System.setProperty("http.keepAlive", originalKeepAlive);
+            }
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mNsm = (NetworkStatsManager) getInstrumentation().getContext()
+                .getSystemService(Context.NETWORK_STATS_SERVICE);
+        mNsm.setPollForce(true);
+
+        mCm = (ConnectivityManager) getInstrumentation().getContext()
+                .getSystemService(Context.CONNECTIVITY_SERVICE);
+
+        mPm = getInstrumentation().getContext().getPackageManager();
+
+        mPkg = getInstrumentation().getContext().getPackageName();
+
+        mWriteSettingsMode = getAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS);
+        setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, "allow");
+        mUsageStatsMode = getAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mWriteSettingsMode != null) {
+            setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, mWriteSettingsMode);
+        }
+        if (mUsageStatsMode != null) {
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, mUsageStatsMode);
+        }
+        super.tearDown();
+    }
+
+    private void setAppOpsMode(String appop, String mode) throws Exception {
+        final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND, mPkg, appop, mode);
+        SystemUtil.runShellCommand(command);
+    }
+
+    private String getAppOpsMode(String appop) throws Exception {
+        final String command = MessageFormat.format(APPOPS_GET_SHELL_COMMAND, mPkg, appop);
+        String result = SystemUtil.runShellCommand(command);
+        if (result == null) {
+            Log.w(LOG_TAG, "App op " + appop + " could not be read.");
+        }
+        return result;
+    }
+
+    private boolean isInForeground() throws IOException {
+        String result = SystemUtil.runShellCommand(getInstrumentation(),
+                "cmd activity get-uid-state " + Process.myUid());
+        return result.contains("FOREGROUND");
+    }
+
+    private class NetworkCallback extends ConnectivityManager.NetworkCallback {
+        private long mTolerance;
+        private URL mUrl;
+        public boolean success;
+        public boolean metered;
+        public boolean isDefault;
+
+        NetworkCallback(long tolerance, URL url) {
+            mTolerance = tolerance;
+            mUrl = url;
+            success = false;
+            metered = false;
+            isDefault = false;
+        }
+
+        // The test host only has IPv4. So on a dual-stack network where IPv6 connects before IPv4,
+        // we need to wait until IPv4 is available or the test will spuriously fail.
+        private void waitForHostResolution(Network network) {
+            for (int i = 0; i < HOST_RESOLUTION_RETRIES; i++) {
+                try {
+                    network.getAllByName(mUrl.getHost());
+                    return;
+                } catch (UnknownHostException e) {
+                    SystemClock.sleep(HOST_RESOLUTION_INTERVAL_MS);
+                }
+            }
+            fail(String.format("%s could not be resolved on network %s (%d attempts %dms apart)",
+                    mUrl.getHost(), network, HOST_RESOLUTION_RETRIES, HOST_RESOLUTION_INTERVAL_MS));
+        }
+
+        @Override
+        public void onAvailable(Network network) {
+            try {
+                mStartTime = System.currentTimeMillis() - mTolerance;
+                isDefault = network.equals(mCm.getActiveNetwork());
+                waitForHostResolution(network);
+                exerciseRemoteHost(network, mUrl);
+                mEndTime = System.currentTimeMillis() + mTolerance;
+                success = true;
+                metered = !mCm.getNetworkCapabilities(network)
+                        .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+                synchronized (NetworkStatsManagerTest.this) {
+                    NetworkStatsManagerTest.this.notify();
+                }
+            } catch (Exception e) {
+                Log.w(LOG_TAG, "exercising remote host failed.", e);
+                success = false;
+            }
+        }
+    }
+
+    private boolean shouldTestThisNetworkType(int networkTypeIndex, final long tolerance)
+            throws Exception {
+        boolean hasFeature = mPm.hasSystemFeature(
+                mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature());
+        if (!hasFeature) {
+            return false;
+        }
+        NetworkCallback callback = new NetworkCallback(tolerance, new URL(CHECK_CONNECTIVITY_URL));
+        mCm.requestNetwork(new NetworkRequest.Builder()
+                .addTransportType(mNetworkInterfacesToTest[networkTypeIndex].getTransportType())
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .build(), callback);
+        synchronized (this) {
+            try {
+                wait((int) (TIMEOUT_MILLIS * 1.2));
+            } catch (InterruptedException e) {
+            }
+        }
+        if (callback.success) {
+            mNetworkInterfacesToTest[networkTypeIndex].setMetered(callback.metered);
+            mNetworkInterfacesToTest[networkTypeIndex].setIsDefault(callback.isDefault);
+            return true;
+        }
+
+        // This will always fail at this point as we know 'hasFeature' is true.
+        assertFalse(mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature()
+                + " is a reported system feature, "
+                + "however no corresponding connected network interface was found or the attempt "
+                + "to connect has timed out (timeout = " + TIMEOUT_MILLIS + "ms)."
+                + mNetworkInterfacesToTest[networkTypeIndex].getErrorMessage(), hasFeature);
+        return false;
+    }
+
+    private String getSubscriberId(int networkIndex) {
+        int networkType = mNetworkInterfacesToTest[networkIndex].getNetworkType();
+        if (ConnectivityManager.TYPE_MOBILE == networkType) {
+            TelephonyManager tm = (TelephonyManager) getInstrumentation().getContext()
+                    .getSystemService(Context.TELEPHONY_SERVICE);
+            return ShellIdentityUtils.invokeMethodWithShellPermissions(tm,
+                    (telephonyManager) -> telephonyManager.getSubscriberId());
+        }
+        return "";
+    }
+
+    @AppModeFull
+    public void testDeviceSummary() throws Exception {
+        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+            if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
+                continue;
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+            NetworkStats.Bucket bucket = null;
+            try {
+                bucket = mNsm.querySummaryForDevice(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime);
+            } catch (RemoteException | SecurityException e) {
+                fail("testDeviceSummary fails with exception: " + e.toString());
+            }
+            assertNotNull(bucket);
+            assertTimestamps(bucket);
+            assertEquals(bucket.getState(), STATE_ALL);
+            assertEquals(bucket.getUid(), UID_ALL);
+            assertEquals(bucket.getMetered(), METERED_ALL);
+            assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+            try {
+                bucket = mNsm.querySummaryForDevice(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime);
+                fail("negative testDeviceSummary fails: no exception thrown.");
+            } catch (RemoteException e) {
+                fail("testDeviceSummary fails with exception: " + e.toString());
+            } catch (SecurityException e) {
+                // expected outcome
+            }
+        }
+    }
+
+    @AppModeFull
+    public void testUserSummary() throws Exception {
+        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+            if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
+                continue;
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+            NetworkStats.Bucket bucket = null;
+            try {
+                bucket = mNsm.querySummaryForUser(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime);
+            } catch (RemoteException | SecurityException e) {
+                fail("testUserSummary fails with exception: " + e.toString());
+            }
+            assertNotNull(bucket);
+            assertTimestamps(bucket);
+            assertEquals(bucket.getState(), STATE_ALL);
+            assertEquals(bucket.getUid(), UID_ALL);
+            assertEquals(bucket.getMetered(), METERED_ALL);
+            assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+            try {
+                bucket = mNsm.querySummaryForUser(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime);
+                fail("negative testUserSummary fails: no exception thrown.");
+            } catch (RemoteException e) {
+                fail("testUserSummary fails with exception: " + e.toString());
+            } catch (SecurityException e) {
+                // expected outcome
+            }
+        }
+    }
+
+    @AppModeFull
+    public void testAppSummary() throws Exception {
+        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+            // Use tolerance value that large enough to make sure stats of at
+            // least one bucket is included. However, this is possible that
+            // the test will see data of different app but with the same UID
+            // that created before testing.
+            // TODO: Consider query stats before testing and use the difference to verify.
+            if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+                continue;
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+            NetworkStats result = null;
+            try {
+                result = mNsm.querySummary(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime);
+                assertNotNull(result);
+                NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+                long totalTxPackets = 0;
+                long totalRxPackets = 0;
+                long totalTxBytes = 0;
+                long totalRxBytes = 0;
+                boolean hasCorrectMetering = false;
+                boolean hasCorrectDefaultStatus = false;
+                int expectedMetering = mNetworkInterfacesToTest[i].getMetered()
+                        ? METERED_YES : METERED_NO;
+                int expectedDefaultStatus = mNetworkInterfacesToTest[i].getIsDefault()
+                        ? DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO;
+                while (result.hasNextBucket()) {
+                    assertTrue(result.getNextBucket(bucket));
+                    assertTimestamps(bucket);
+                    hasCorrectMetering |= bucket.getMetered() == expectedMetering;
+                    if (bucket.getUid() == Process.myUid()) {
+                        totalTxPackets += bucket.getTxPackets();
+                        totalRxPackets += bucket.getRxPackets();
+                        totalTxBytes += bucket.getTxBytes();
+                        totalRxBytes += bucket.getRxBytes();
+                        hasCorrectDefaultStatus |=
+                                bucket.getDefaultNetworkStatus() == expectedDefaultStatus;
+                    }
+                }
+                assertFalse(result.getNextBucket(bucket));
+                assertTrue("Incorrect metering for NetworkType: "
+                        + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectMetering);
+                assertTrue("Incorrect isDefault for NetworkType: "
+                        + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectDefaultStatus);
+                assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
+                assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0);
+                assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0);
+                assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0);
+            } finally {
+                if (result != null) {
+                    result.close();
+                }
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+            try {
+                result = mNsm.querySummary(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime);
+                fail("negative testAppSummary fails: no exception thrown.");
+            } catch (RemoteException e) {
+                fail("testAppSummary fails with exception: " + e.toString());
+            } catch (SecurityException e) {
+                // expected outcome
+            }
+        }
+    }
+
+    @AppModeFull
+    public void testAppDetails() throws Exception {
+        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+            // Relatively large tolerance to accommodate for history bucket size.
+            if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+                continue;
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+            NetworkStats result = null;
+            try {
+                result = mNsm.queryDetails(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime);
+                long totalBytesWithSubscriberId = getTotalAndAssertNotEmpty(result);
+
+                // Test without filtering by subscriberId
+                result = mNsm.queryDetails(
+                        mNetworkInterfacesToTest[i].getNetworkType(), null,
+                        mStartTime, mEndTime);
+
+                assertTrue("More bytes with subscriberId filter than without.",
+                        getTotalAndAssertNotEmpty(result) >= totalBytesWithSubscriberId);
+            } catch (RemoteException | SecurityException e) {
+                fail("testAppDetails fails with exception: " + e.toString());
+            } finally {
+                if (result != null) {
+                    result.close();
+                }
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+            try {
+                result = mNsm.queryDetails(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime);
+                fail("negative testAppDetails fails: no exception thrown.");
+            } catch (RemoteException e) {
+                fail("testAppDetails fails with exception: " + e.toString());
+            } catch (SecurityException e) {
+                // expected outcome
+            }
+        }
+    }
+
+    @AppModeFull
+    public void testUidDetails() throws Exception {
+        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+            // Relatively large tolerance to accommodate for history bucket size.
+            if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+                continue;
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+            NetworkStats result = null;
+            try {
+                result = mNsm.queryDetailsForUid(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime, Process.myUid());
+                assertNotNull(result);
+                NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+                long totalTxPackets = 0;
+                long totalRxPackets = 0;
+                long totalTxBytes = 0;
+                long totalRxBytes = 0;
+                while (result.hasNextBucket()) {
+                    assertTrue(result.getNextBucket(bucket));
+                    assertTimestamps(bucket);
+                    assertEquals(bucket.getState(), STATE_ALL);
+                    assertEquals(bucket.getMetered(), METERED_ALL);
+                    assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+                    assertEquals(bucket.getUid(), Process.myUid());
+                    totalTxPackets += bucket.getTxPackets();
+                    totalRxPackets += bucket.getRxPackets();
+                    totalTxBytes += bucket.getTxBytes();
+                    totalRxBytes += bucket.getRxBytes();
+                }
+                assertFalse(result.getNextBucket(bucket));
+                assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
+                assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0);
+                assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0);
+                assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0);
+            } finally {
+                if (result != null) {
+                    result.close();
+                }
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+            try {
+                result = mNsm.queryDetailsForUid(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime, Process.myUid());
+                fail("negative testUidDetails fails: no exception thrown.");
+            } catch (SecurityException e) {
+                // expected outcome
+            }
+        }
+    }
+
+    @AppModeFull
+    public void testTagDetails() throws Exception {
+        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+            // Relatively large tolerance to accommodate for history bucket size.
+            if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+                continue;
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+            NetworkStats result = null;
+            try {
+                result = mNsm.queryDetailsForUidTag(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
+                assertNotNull(result);
+                NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+                long totalTxPackets = 0;
+                long totalRxPackets = 0;
+                long totalTxBytes = 0;
+                long totalRxBytes = 0;
+                while (result.hasNextBucket()) {
+                    assertTrue(result.getNextBucket(bucket));
+                    assertTimestamps(bucket);
+                    assertEquals(bucket.getState(), STATE_ALL);
+                    assertEquals(bucket.getMetered(), METERED_ALL);
+                    assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+                    assertEquals(bucket.getUid(), Process.myUid());
+                    if (bucket.getTag() == NETWORK_TAG) {
+                        totalTxPackets += bucket.getTxPackets();
+                        totalRxPackets += bucket.getRxPackets();
+                        totalTxBytes += bucket.getTxBytes();
+                        totalRxBytes += bucket.getRxBytes();
+                    }
+                }
+                assertTrue("No Rx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG)
+                        + " for uid " + Process.myUid(), totalRxBytes > 0);
+                assertTrue("No Rx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG)
+                        + " for uid " + Process.myUid(), totalRxPackets > 0);
+                assertTrue("No Tx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG)
+                        + " for uid " + Process.myUid(), totalTxBytes > 0);
+                assertTrue("No Tx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG)
+                        + " for uid " + Process.myUid(), totalTxPackets > 0);
+            } finally {
+                if (result != null) {
+                    result.close();
+                }
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+            try {
+                result = mNsm.queryDetailsForUidTag(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
+                fail("negative testUidDetails fails: no exception thrown.");
+            } catch (SecurityException e) {
+                // expected outcome
+            }
+        }
+    }
+
+    class QueryResult {
+        public final int tag;
+        public final int state;
+        public final long total;
+
+        QueryResult(int tag, int state, NetworkStats stats) {
+            this.tag = tag;
+            this.state = state;
+            total = getTotalAndAssertNotEmpty(stats, tag, state);
+        }
+
+        public String toString() {
+            return String.format("QueryResult(tag=%s state=%s total=%d)",
+                    tagToString(tag), stateToString(state), total);
+        }
+    }
+
+    private NetworkStats getNetworkStatsForTagState(int i, int tag, int state) {
+        return mNsm.queryDetailsForUidTagState(
+                mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                mStartTime, mEndTime, Process.myUid(), tag, state);
+    }
+
+    private void assertWithinPercentage(String msg, long expected, long actual, int percentage) {
+        long lowerBound = expected * (100 - percentage) / 100;
+        long upperBound = expected * (100 + percentage) / 100;
+        msg = String.format("%s: %d not within %d%% of %d", msg, actual, percentage, expected);
+        assertTrue(msg, lowerBound <= actual);
+        assertTrue(msg, upperBound >= actual);
+    }
+
+    private void assertAlmostNoUnexpectedTraffic(NetworkStats result, int expectedTag,
+            int expectedState, long maxUnexpected) {
+        long total = 0;
+        NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+        while (result.hasNextBucket()) {
+            assertTrue(result.getNextBucket(bucket));
+            total += bucket.getRxBytes() + bucket.getTxBytes();
+        }
+        if (total <= maxUnexpected) return;
+
+        fail(String.format("More than %d bytes of traffic when querying for "
+                + "tag %s state %s. Last bucket: uid=%d tag=%s state=%s bytes=%d/%d",
+                maxUnexpected, tagToString(expectedTag), stateToString(expectedState),
+                bucket.getUid(), tagToString(bucket.getTag()), stateToString(bucket.getState()),
+                bucket.getRxBytes(), bucket.getTxBytes()));
+    }
+
+    @AppModeFull
+    public void testUidTagStateDetails() throws Exception {
+        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+            // Relatively large tolerance to accommodate for history bucket size.
+            if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+                continue;
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+            NetworkStats result = null;
+            try {
+                int currentState = isInForeground() ? STATE_FOREGROUND : STATE_DEFAULT;
+                int otherState = (currentState == STATE_DEFAULT) ? STATE_FOREGROUND : STATE_DEFAULT;
+
+                int[] tagsWithTraffic = {NETWORK_TAG, TAG_NONE};
+                int[] statesWithTraffic = {currentState, STATE_ALL};
+                ArrayList<QueryResult> resultsWithTraffic = new ArrayList<>();
+
+                int[] statesWithNoTraffic = {otherState};
+                int[] tagsWithNoTraffic = {NETWORK_TAG + 1};
+                ArrayList<QueryResult> resultsWithNoTraffic = new ArrayList<>();
+
+                // Expect to see traffic when querying for any combination of a tag in
+                // tagsWithTraffic and a state in statesWithTraffic.
+                for (int tag : tagsWithTraffic) {
+                    for (int state : statesWithTraffic) {
+                        result = getNetworkStatsForTagState(i, tag, state);
+                        resultsWithTraffic.add(new QueryResult(tag, state, result));
+                        result.close();
+                        result = null;
+                    }
+                }
+
+                // Expect that the results are within a few percentage points of each other.
+                // This is ensures that FIN retransmits after the transfer is complete don't cause
+                // the test to be flaky. The test URL currently returns just over 100k so this
+                // should not be too noisy. It also ensures that the traffic sent by the test
+                // harness, which is untagged, won't cause a failure.
+                long firstTotal = resultsWithTraffic.get(0).total;
+                for (QueryResult queryResult : resultsWithTraffic) {
+                    assertWithinPercentage(queryResult + "", firstTotal, queryResult.total, 10);
+                }
+
+                // Expect to see no traffic when querying for any tag in tagsWithNoTraffic or any
+                // state in statesWithNoTraffic.
+                for (int tag : tagsWithNoTraffic) {
+                    for (int state : statesWithTraffic) {
+                        result = getNetworkStatsForTagState(i, tag, state);
+                        assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100);
+                        result.close();
+                        result = null;
+                    }
+                }
+                for (int tag : tagsWithTraffic) {
+                    for (int state : statesWithNoTraffic) {
+                        result = getNetworkStatsForTagState(i, tag, state);
+                        assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100);
+                        result.close();
+                        result = null;
+                    }
+                }
+            } finally {
+                if (result != null) {
+                    result.close();
+                }
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+            try {
+                result = mNsm.queryDetailsForUidTag(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
+                fail("negative testUidDetails fails: no exception thrown.");
+            } catch (SecurityException e) {
+                // expected outcome
+            }
+        }
+    }
+
+    @AppModeFull
+    public void testCallback() throws Exception {
+        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+            // Relatively large tolerance to accommodate for history bucket size.
+            if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
+                continue;
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+
+            TestUsageCallback usageCallback = new TestUsageCallback();
+            HandlerThread thread = new HandlerThread("callback-thread");
+            thread.start();
+            Handler handler = new Handler(thread.getLooper());
+            mNsm.registerUsageCallback(mNetworkInterfacesToTest[i].getNetworkType(),
+                    getSubscriberId(i), THRESHOLD_BYTES, usageCallback, handler);
+
+            // TODO: Force traffic and check whether the callback is invoked.
+            // Right now the test only covers whether the callback can be registered, but not
+            // whether it is invoked upon data usage since we don't have a scalable way of
+            // storing files of >2MB in CTS.
+
+            mNsm.unregisterUsageCallback(usageCallback);
+        }
+    }
+
+    private String tagToString(Integer tag) {
+        if (tag == null) return "null";
+        switch (tag) {
+            case TAG_NONE:
+                return "TAG_NONE";
+            default:
+                return "0x" + Integer.toHexString(tag);
+        }
+    }
+
+    private String stateToString(Integer state) {
+        if (state == null) return "null";
+        switch (state) {
+            case STATE_ALL:
+                return "STATE_ALL";
+            case STATE_DEFAULT:
+                return "STATE_DEFAULT";
+            case STATE_FOREGROUND:
+                return "STATE_FOREGROUND";
+        }
+        throw new IllegalArgumentException("Unknown state " + state);
+    }
+
+    private long getTotalAndAssertNotEmpty(NetworkStats result, Integer expectedTag,
+            Integer expectedState) {
+        assertTrue(result != null);
+        NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+        long totalTxPackets = 0;
+        long totalRxPackets = 0;
+        long totalTxBytes = 0;
+        long totalRxBytes = 0;
+        while (result.hasNextBucket()) {
+            assertTrue(result.getNextBucket(bucket));
+            assertTimestamps(bucket);
+            if (expectedTag != null) assertEquals(bucket.getTag(), (int) expectedTag);
+            if (expectedState != null) assertEquals(bucket.getState(), (int) expectedState);
+            assertEquals(bucket.getMetered(), METERED_ALL);
+            assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+            if (bucket.getUid() == Process.myUid()) {
+                totalTxPackets += bucket.getTxPackets();
+                totalRxPackets += bucket.getRxPackets();
+                totalTxBytes += bucket.getTxBytes();
+                totalRxBytes += bucket.getRxBytes();
+            }
+        }
+        assertFalse(result.getNextBucket(bucket));
+        String msg = String.format("uid %d tag %s state %s",
+                Process.myUid(), tagToString(expectedTag), stateToString(expectedState));
+        assertTrue("No Rx bytes usage for " + msg, totalRxBytes > 0);
+        assertTrue("No Rx packets usage for " + msg, totalRxPackets > 0);
+        assertTrue("No Tx bytes usage for " + msg, totalTxBytes > 0);
+        assertTrue("No Tx packets usage for " + msg, totalTxPackets > 0);
+
+        return totalRxBytes + totalTxBytes;
+    }
+
+    private long getTotalAndAssertNotEmpty(NetworkStats result) {
+        return getTotalAndAssertNotEmpty(result, null, STATE_ALL);
+    }
+
+    private void assertTimestamps(final NetworkStats.Bucket bucket) {
+        assertTrue("Start timestamp " + bucket.getStartTimeStamp() + " is less than "
+                + mStartTime, bucket.getStartTimeStamp() >= mStartTime);
+        assertTrue("End timestamp " + bucket.getEndTimeStamp() + " is greater than "
+                + mEndTime, bucket.getEndTimeStamp() <= mEndTime);
+    }
+
+    private static class TestUsageCallback extends NetworkStatsManager.UsageCallback {
+        @Override
+        public void onThresholdReached(int networkType, String subscriberId) {
+            Log.v(LOG_TAG, "Called onThresholdReached for networkType=" + networkType
+                    + " subscriberId=" + subscriberId);
+        }
+    }
+}
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 9307c27..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 serviceName = "NsdTest%04d".format(Random().nextInt(1000))
+
+    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/integration/Android.bp b/tests/integration/Android.bp
index 4d4e7b9..530fa91 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -53,7 +53,8 @@
         // android_library does not include JNI libs: include NetworkStack dependencies here
         "libnativehelper_compat_libc++",
         "libnetworkstackutilsjni",
-        "libcom_android_connectivity_com_android_net_module_util_jni",
+        "libandroid_net_connectivity_com_android_net_module_util_jni",
+        "libservice-connectivity",
     ],
     jarjar_rules: ":connectivity-jarjar-rules",
 }
diff --git a/tests/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
index 4dc86ff..365c0cf 100644
--- a/tests/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -21,6 +21,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
@@ -109,6 +110,9 @@
             case TRANSPORT_WIFI_AWARE:
                 mScore = new NetworkScore.Builder().setLegacyInt(20).build();
                 break;
+            case TRANSPORT_TEST:
+                mScore = new NetworkScore.Builder().build();
+                break;
             case TRANSPORT_VPN:
                 mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN);
                 // VPNs deduce the SUSPENDED capability from their underlying networks and there
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 25b391a..fde7bac 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -27,7 +27,6 @@
         "libbase",
         "libbinder",
         "libbpf_bcc",
-        "libbpf_android",
         "libc++",
         "libcgrouprc",
         "libcrypto",
@@ -107,8 +106,8 @@
     visibility: ["//visibility:private"],
 }
 
-android_library {
-    name: "FrameworksNetTestsLib",
+java_defaults {
+    name: "FrameworksNetTestsDefaults",
     min_sdk_version: "30",
     defaults: [
         "framework-connectivity-test-defaults",
@@ -117,8 +116,6 @@
         "java/**/*.java",
         "java/**/*.kt",
     ],
-    exclude_srcs: [":non-connectivity-module-test"],
-    jarjar_rules: "jarjar-rules.txt",
     static_libs: [
         "androidx.test.rules",
         "androidx.test.uiautomator",
@@ -144,31 +141,33 @@
         "android.test.mock",
         "ServiceConnectivityResources",
     ],
-    visibility: ["//packages/modules/Connectivity/tests:__subpackages__"],
     exclude_kotlinc_generated_files: false,
 }
 
+android_library {
+    name: "FrameworksNetTestsLib",
+    defaults: [
+        "FrameworksNetTestsDefaults",
+    ],
+    exclude_srcs: [":non-connectivity-module-test"],
+    visibility: ["//packages/modules/Connectivity/tests:__subpackages__"],
+}
+
 android_test {
     name: "FrameworksNetTests",
     enabled: enable_frameworks_net_tests,
-    min_sdk_version: "30",
     defaults: [
-        "framework-connectivity-test-defaults",
+        "FrameworksNetTestsDefaults",
         "FrameworksNetTests-jni-defaults",
     ],
-    // this is in addition to FrameworksNetTestsLib.
-    srcs: [":non-connectivity-module-test"],
+    jarjar_rules: ":connectivity-jarjar-rules",
     test_suites: ["device-tests"],
     static_libs: [
         "services.core",
         "services.net",
-        "FrameworksNetTestsLib",
-    ],
-    libs: [
-        "android.test.mock",
-        "android.test.base",
     ],
     jni_libs: [
         "libservice-connectivity",
-    ]
+        "libandroid_net_connectivity_com_android_net_module_util_jni",
+    ],
 }
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/Ikev2VpnProfileTest.java b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
index a151f03..8559c20 100644
--- a/tests/unit/java/android/net/Ikev2VpnProfileTest.java
+++ b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -264,17 +266,17 @@
     }
 
 
-    // TODO: Refer to Build.VERSION_CODES.SC_V2 when it's available in AOSP
-    @DevSdkIgnoreRule.IgnoreUpTo(32)
+    // TODO: Refer to Build.VERSION_CODES.SC_V2 when it's available in AOSP and mainline branch
+    @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
     @Test
     public void testBuildExcludeLocalRoutesSet() throws Exception {
         final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
         builder.setAuthPsk(PSK_BYTES);
-        builder.setExcludeLocalRoutes(true);
+        builder.setLocalRoutesExcluded(true);
 
         final Ikev2VpnProfile profile = builder.build();
         assertNotNull(profile);
-        assertTrue(profile.getExcludeLocalRoutes());
+        assertTrue(profile.areLocalRoutesExcluded());
 
         builder.setBypassable(false);
         try {
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/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
new file mode 100644
index 0000000..ac21e77
--- /dev/null
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.net.INetd.FIREWALL_CHAIN_DOZABLE;
+import static android.net.INetd.FIREWALL_RULE_ALLOW;
+import static android.net.INetd.PERMISSION_INTERNET;
+
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.Mockito.verify;
+
+import android.net.INetd;
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.modules.utils.build.SdkLevel;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+public final class BpfNetMapsTest {
+    private static final String TAG = "BpfNetMapsTest";
+    private static final int TEST_UID = 10086;
+    private static final int[] TEST_UIDS = {10002, 10003};
+    private static final String IFNAME = "wlan0";
+    private static final String CHAINNAME = "fw_dozable";
+    private BpfNetMaps mBpfNetMaps;
+
+    @Mock INetd mNetd;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mBpfNetMaps = new BpfNetMaps(mNetd);
+    }
+
+    @Test
+    public void testBpfNetMapsBeforeT() throws Exception {
+        assumeFalse(SdkLevel.isAtLeastT());
+        mBpfNetMaps.addNaughtyApp(TEST_UID);
+        verify(mNetd).bandwidthAddNaughtyApp(TEST_UID);
+        mBpfNetMaps.removeNaughtyApp(TEST_UID);
+        verify(mNetd).bandwidthRemoveNaughtyApp(TEST_UID);
+        mBpfNetMaps.addNiceApp(TEST_UID);
+        verify(mNetd).bandwidthAddNiceApp(TEST_UID);
+        mBpfNetMaps.removeNiceApp(TEST_UID);
+        verify(mNetd).bandwidthRemoveNiceApp(TEST_UID);
+        mBpfNetMaps.setChildChain(FIREWALL_CHAIN_DOZABLE, true);
+        verify(mNetd).firewallEnableChildChain(FIREWALL_CHAIN_DOZABLE, true);
+        mBpfNetMaps.replaceUidChain(CHAINNAME, true, TEST_UIDS);
+        verify(mNetd).firewallReplaceUidChain(CHAINNAME, true, TEST_UIDS);
+        mBpfNetMaps.setUidRule(FIREWALL_CHAIN_DOZABLE, TEST_UID, FIREWALL_RULE_ALLOW);
+        verify(mNetd).firewallSetUidRule(FIREWALL_CHAIN_DOZABLE, TEST_UID, FIREWALL_RULE_ALLOW);
+        mBpfNetMaps.addUidInterfaceRules(IFNAME, TEST_UIDS);
+        verify(mNetd).firewallAddUidInterfaceRules(IFNAME, TEST_UIDS);
+        mBpfNetMaps.removeUidInterfaceRules(TEST_UIDS);
+        verify(mNetd).firewallRemoveUidInterfaceRules(TEST_UIDS);
+        mBpfNetMaps.swapActiveStatsMap();
+        verify(mNetd).trafficSwapActiveStatsMap();
+        mBpfNetMaps.setNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
+        verify(mNetd).trafficSetNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
+    }
+}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 652aee9..16b3d5a 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -23,6 +23,7 @@
 import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.GET_INTENT_SENDER_INTENT;
 import static android.Manifest.permission.LOCAL_MAC_ADDRESS;
+import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
 import static android.Manifest.permission.NETWORK_FACTORY;
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.NETWORK_STACK;
@@ -108,6 +109,7 @@
 import static android.net.NetworkCapabilities.REDACT_NONE;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
@@ -177,7 +179,6 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
 
 import static java.util.Arrays.asList;
 
@@ -262,6 +263,7 @@
 import android.net.RouteInfo;
 import android.net.RouteInfoParcel;
 import android.net.SocketKeepalive;
+import android.net.TelephonyNetworkSpecifier;
 import android.net.TransportInfo;
 import android.net.UidRange;
 import android.net.UidRangeParcel;
@@ -332,6 +334,7 @@
 import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
 import com.android.server.ConnectivityService.NetworkRequestInfo;
 import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencies.ReportedInterfaces;
+import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
 import com.android.server.connectivity.ConnectivityFlags;
 import com.android.server.connectivity.MockableSystemProperties;
 import com.android.server.connectivity.Nat464Xlat;
@@ -384,6 +387,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
@@ -526,6 +530,8 @@
     @Mock SystemConfigManager mSystemConfigManager;
     @Mock Resources mResources;
     @Mock PacProxyManager mPacProxyManager;
+    @Mock BpfNetMaps mBpfNetMaps;
+    @Mock CarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
 
     // BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the
     // underlying binder calls.
@@ -901,7 +907,7 @@
                 return null;
             };
 
-            doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected(any(), any());
+            doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnectedParcel(any());
             doAnswer(validateAnswer).when(mNetworkMonitor).forceReevaluation(anyInt());
 
             final ArgumentCaptor<Network> nmNetworkCaptor = ArgumentCaptor.forClass(Network.class);
@@ -967,8 +973,6 @@
          * @param hasInternet Indicate if network should pretend to have NET_CAPABILITY_INTERNET.
          */
         public void connect(boolean validated, boolean hasInternet, boolean isStrictMode) {
-            assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_INTERNET));
-
             ConnectivityManager.NetworkCallback callback = null;
             final ConditionVariable validatedCv = new ConditionVariable();
             if (validated) {
@@ -1490,6 +1494,10 @@
                 r -> new UidRangeParcel(r.start, r.stop)).toArray(UidRangeParcel[]::new);
     }
 
+    private UidRangeParcel[] intToUidRangeStableParcels(final @NonNull Set<Integer> ranges) {
+        return ranges.stream().map(r -> new UidRangeParcel(r, r)).toArray(UidRangeParcel[]::new);
+    }
+
     private VpnManagerService makeVpnManagerService() {
         final VpnManagerService.Dependencies deps = new VpnManagerService.Dependencies() {
             public int getCallingUid() {
@@ -1852,6 +1860,12 @@
         }
 
         @Override
+        public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
+                @NonNull final Context context, @NonNull final TelephonyManager tm) {
+            return SdkLevel.isAtLeastT() ? mCarrierPrivilegeAuthenticator : null;
+        }
+
+        @Override
         public boolean intentFilterEquals(final PendingIntent a, final PendingIntent b) {
             return runAsShell(GET_INTENT_SENDER_INTENT, () -> a.intentFilterEquals(b));
         }
@@ -1944,6 +1958,30 @@
                     return super.isFeatureEnabled(context, name, defaultEnabled);
             }
         }
+
+        @Override
+        public BpfNetMaps getBpfNetMaps(INetd netd) {
+            return mBpfNetMaps;
+        }
+
+        final ArrayTrackRecord<Pair<String, Long>> mRateLimitHistory = new ArrayTrackRecord<>();
+        final Map<String, Long> mActiveRateLimit = new HashMap<>();
+
+        @Override
+        public void enableIngressRateLimit(final String iface, final long rateInBytesPerSecond) {
+            mRateLimitHistory.add(new Pair<>(iface, rateInBytesPerSecond));
+            // Due to a TC limitation, the rate limit needs to be removed before it can be
+            // updated. Check that this happened.
+            assertEquals(-1L, (long) mActiveRateLimit.getOrDefault(iface, -1L));
+            mActiveRateLimit.put(iface, rateInBytesPerSecond);
+        }
+
+        @Override
+        public void disableIngressRateLimit(final String iface) {
+            mRateLimitHistory.add(new Pair<>(iface, -1L));
+            assertNotEquals(-1L, (long) mActiveRateLimit.getOrDefault(iface, -1L));
+            mActiveRateLimit.put(iface, -1L);
+        }
     }
 
     private static void initAlarmManager(final AlarmManager am, final Handler alarmHandler) {
@@ -3813,14 +3851,14 @@
     public void testNoMutableNetworkRequests() throws Exception {
         final PendingIntent pendingIntent = PendingIntent.getBroadcast(
                 mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
-        NetworkRequest request1 = new NetworkRequest.Builder()
+        final NetworkRequest request1 = new NetworkRequest.Builder()
                 .addCapability(NET_CAPABILITY_VALIDATED)
                 .build();
-        NetworkRequest request2 = new NetworkRequest.Builder()
+        final NetworkRequest request2 = new NetworkRequest.Builder()
                 .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL)
                 .build();
 
-        Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+        final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
         assertThrows(expected, () -> mCm.requestNetwork(request1, new NetworkCallback()));
         assertThrows(expected, () -> mCm.requestNetwork(request1, pendingIntent));
         assertThrows(expected, () -> mCm.requestNetwork(request2, new NetworkCallback()));
@@ -3828,6 +3866,36 @@
     }
 
     @Test
+    public void testNoAccessUidsInNetworkRequests() throws Exception {
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
+        final NetworkRequest r = new NetworkRequest.Builder().build();
+        final ArraySet<Integer> accessUids = new ArraySet<>();
+        accessUids.add(6);
+        accessUids.add(9);
+        r.networkCapabilities.setAccessUids(accessUids);
+
+        final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+        final NetworkCallback cb = new NetworkCallback();
+
+        final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+        assertThrows(expected, () -> mCm.requestNetwork(r, cb));
+        assertThrows(expected, () -> mCm.requestNetwork(r, pendingIntent));
+        assertThrows(expected, () -> mCm.registerNetworkCallback(r, cb));
+        assertThrows(expected, () -> mCm.registerNetworkCallback(r, cb, handler));
+        assertThrows(expected, () -> mCm.registerNetworkCallback(r, pendingIntent));
+        assertThrows(expected, () -> mCm.registerBestMatchingNetworkCallback(r, cb, handler));
+
+        // Make sure that resetting the access UIDs to the empty set will allow calling
+        // requestNetwork and registerNetworkCallback.
+        r.networkCapabilities.setAccessUids(Collections.emptySet());
+        mCm.requestNetwork(r, cb);
+        mCm.unregisterNetworkCallback(cb);
+        mCm.registerNetworkCallback(r, cb);
+        mCm.unregisterNetworkCallback(cb);
+    }
+
+    @Test
     public void testMMSonWiFi() throws Exception {
         // Test bringing up cellular without MMS NetworkRequest gets reaped
         mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
@@ -4978,6 +5046,13 @@
         waitForIdle();
     }
 
+    private void setIngressRateLimit(int rateLimitInBytesPerSec) {
+        ConnectivitySettingsManager.setIngressRateLimitInBytesPerSecond(mServiceContext,
+                rateLimitInBytesPerSec);
+        mService.updateIngressRateLimit();
+        waitForIdle();
+    }
+
     private boolean isForegroundNetwork(TestNetworkAgentWrapper network) {
         NetworkCapabilities nc = mCm.getNetworkCapabilities(network.getNetwork());
         assertNotNull(nc);
@@ -5727,6 +5802,22 @@
         }
     }
 
+    /**
+     * Validate the callback flow CBS request without carrier privilege.
+     */
+    @Test
+    public void testCBSRequestWithoutCarrierPrivilege() throws Exception {
+        final NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+                TRANSPORT_CELLULAR).addCapability(NET_CAPABILITY_CBS).build();
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+
+        mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED);
+        // Now file the test request and expect it.
+        mCm.requestNetwork(nr, networkCallback);
+        networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null);
+        mCm.unregisterNetworkCallback(networkCallback);
+    }
+
     private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
 
         public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }
@@ -10074,7 +10165,7 @@
         // A connected VPN should have interface rules set up. There are two expected invocations,
         // one during the VPN initial connection, one during the VPN LinkProperties update.
         ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
-        verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+        verify(mBpfNetMaps, times(2)).addUidInterfaceRules(eq("tun0"), uidCaptor.capture());
         assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
         assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
         assertTrue(mService.mPermissionMonitor.getVpnUidRanges("tun0").equals(vpnRange));
@@ -10083,7 +10174,7 @@
         waitForIdle();
 
         // Disconnected VPN should have interface rules removed
-        verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+        verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
         assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
         assertNull(mService.mPermissionMonitor.getVpnUidRanges("tun0"));
     }
@@ -10100,7 +10191,7 @@
         assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
 
         // Legacy VPN should not have interface rules set up
-        verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
+        verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
     }
 
     @Test
@@ -10116,7 +10207,7 @@
         assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
 
         // IPv6 unreachable route should not be misinterpreted as a default route
-        verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
+        verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
     }
 
     @Test
@@ -10133,33 +10224,33 @@
         // Connected VPN should have interface rules set up. There are two expected invocations,
         // one during VPN uid update, one during VPN LinkProperties update
         ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
-        verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+        verify(mBpfNetMaps, times(2)).addUidInterfaceRules(eq("tun0"), uidCaptor.capture());
         assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
         assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
 
-        reset(mMockNetd);
-        InOrder inOrder = inOrder(mMockNetd);
+        reset(mBpfNetMaps);
+        InOrder inOrder = inOrder(mBpfNetMaps);
         lp.setInterfaceName("tun1");
         mMockVpn.sendLinkProperties(lp);
         waitForIdle();
         // VPN handover (switch to a new interface) should result in rules being updated (old rules
         // removed first, then new rules added)
-        inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+        inOrder.verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
         assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
-        inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
+        inOrder.verify(mBpfNetMaps).addUidInterfaceRules(eq("tun1"), uidCaptor.capture());
         assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
 
-        reset(mMockNetd);
+        reset(mBpfNetMaps);
         lp = new LinkProperties();
         lp.setInterfaceName("tun1");
         lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun1"));
         mMockVpn.sendLinkProperties(lp);
         waitForIdle();
         // VPN not routing everything should no longer have interface filtering rules
-        verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+        verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
         assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
 
-        reset(mMockNetd);
+        reset(mBpfNetMaps);
         lp = new LinkProperties();
         lp.setInterfaceName("tun1");
         lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
@@ -10167,7 +10258,7 @@
         mMockVpn.sendLinkProperties(lp);
         waitForIdle();
         // Back to routing all IPv6 traffic should have filtering rules
-        verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
+        verify(mBpfNetMaps).addUidInterfaceRules(eq("tun1"), uidCaptor.capture());
         assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
     }
 
@@ -10196,8 +10287,8 @@
         mMockVpn.establish(lp, VPN_UID, vpnRanges);
         assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID);
 
-        reset(mMockNetd);
-        InOrder inOrder = inOrder(mMockNetd);
+        reset(mBpfNetMaps);
+        InOrder inOrder = inOrder(mBpfNetMaps);
 
         // Update to new range which is old range minus APP1, i.e. only APP2
         final Set<UidRange> newRanges = new HashSet<>(asList(
@@ -10208,9 +10299,9 @@
 
         ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
         // Verify old rules are removed before new rules are added
-        inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+        inOrder.verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
         assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
-        inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+        inOrder.verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), uidCaptor.capture());
         assertContainsExactly(uidCaptor.getValue(), APP2_UID);
     }
 
@@ -14555,6 +14646,189 @@
                 () -> mCm.registerNetworkCallback(getRequestWithSubIds(), new NetworkCallback()));
     }
 
+    @Test
+    public void testAccessUids() throws Exception {
+        final int preferenceOrder =
+                ConnectivityService.PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT;
+        mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+        mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        mCm.requestNetwork(new NetworkRequest.Builder()
+                        .clearCapabilities()
+                        .addTransportType(TRANSPORT_TEST)
+                        .build(),
+                cb);
+
+        final ArraySet<Integer> uids = new ArraySet<>();
+        uids.add(200);
+        final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_TEST)
+                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                .setAccessUids(uids)
+                .build();
+        final TestNetworkAgentWrapper agent = new TestNetworkAgentWrapper(TRANSPORT_TEST,
+                new LinkProperties(), nc);
+        agent.connect(true);
+        cb.expectAvailableThenValidatedCallbacks(agent);
+
+        final InOrder inOrder = inOrder(mMockNetd);
+        final NativeUidRangeConfig uids200Parcel = new NativeUidRangeConfig(
+                agent.getNetwork().getNetId(),
+                intToUidRangeStableParcels(uids),
+                preferenceOrder);
+        if (SdkLevel.isAtLeastT()) {
+            inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids200Parcel);
+        }
+
+        uids.add(300);
+        uids.add(400);
+        nc.setAccessUids(uids);
+        agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+        if (SdkLevel.isAtLeastT()) {
+            cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+        } else {
+            cb.assertNoCallback();
+        }
+
+        uids.remove(200);
+        final NativeUidRangeConfig uids300400Parcel = new NativeUidRangeConfig(
+                agent.getNetwork().getNetId(),
+                intToUidRangeStableParcels(uids),
+                preferenceOrder);
+        if (SdkLevel.isAtLeastT()) {
+            inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids300400Parcel);
+        }
+
+        nc.setAccessUids(uids);
+        agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+        if (SdkLevel.isAtLeastT()) {
+            cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+            inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids200Parcel);
+        } else {
+            cb.assertNoCallback();
+        }
+
+        uids.clear();
+        uids.add(600);
+        nc.setAccessUids(uids);
+        agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+        if (SdkLevel.isAtLeastT()) {
+            cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+        } else {
+            cb.assertNoCallback();
+        }
+        final NativeUidRangeConfig uids600Parcel = new NativeUidRangeConfig(
+                agent.getNetwork().getNetId(),
+                intToUidRangeStableParcels(uids),
+                preferenceOrder);
+        if (SdkLevel.isAtLeastT()) {
+            inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids600Parcel);
+            inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids300400Parcel);
+        }
+
+        uids.clear();
+        nc.setAccessUids(uids);
+        agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+        if (SdkLevel.isAtLeastT()) {
+            cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().isEmpty());
+            inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids600Parcel);
+        } else {
+            cb.assertNoCallback();
+            verify(mMockNetd, never()).networkAddUidRangesParcel(any());
+            verify(mMockNetd, never()).networkRemoveUidRangesParcel(any());
+        }
+
+    }
+
+    @Test
+    public void testCbsAccessUids() throws Exception {
+        mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+        mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
+
+        // In this test TEST_PACKAGE_UID will be the UID of the carrier service UID.
+        doReturn(true).when(mCarrierPrivilegeAuthenticator)
+                .hasCarrierPrivilegeForNetworkCapabilities(eq(TEST_PACKAGE_UID), any());
+
+        final ArraySet<Integer> serviceUidSet = new ArraySet<>();
+        serviceUidSet.add(TEST_PACKAGE_UID);
+        final ArraySet<Integer> nonServiceUidSet = new ArraySet<>();
+        nonServiceUidSet.add(TEST_PACKAGE_UID2);
+        final ArraySet<Integer> serviceUidSetPlus = new ArraySet<>();
+        serviceUidSetPlus.add(TEST_PACKAGE_UID);
+        serviceUidSetPlus.add(TEST_PACKAGE_UID2);
+
+        final TestNetworkCallback cb = new TestNetworkCallback();
+
+        // Simulate a restricted telephony network. The telephony factory is entitled to set
+        // the access UID to the service package on any of its restricted networks.
+        final NetworkCapabilities.Builder ncb = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                .setNetworkSpecifier(new TelephonyNetworkSpecifier(1 /* subid */));
+
+        // Cell gets to set the service UID as access UID
+        mCm.requestNetwork(new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                .build(), cb);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
+                new LinkProperties(), ncb.build());
+        mCellNetworkAgent.connect(true);
+        cb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        ncb.setAccessUids(serviceUidSet);
+        mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+        if (SdkLevel.isAtLeastT()) {
+            cb.expectCapabilitiesThat(mCellNetworkAgent,
+                    caps -> caps.getAccessUids().equals(serviceUidSet));
+        } else {
+            // S must ignore access UIDs.
+            cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
+        }
+
+        // ...but not to some other UID. Rejection sets UIDs to the empty set
+        ncb.setAccessUids(nonServiceUidSet);
+        mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+        if (SdkLevel.isAtLeastT()) {
+            cb.expectCapabilitiesThat(mCellNetworkAgent,
+                    caps -> caps.getAccessUids().isEmpty());
+        } else {
+            // S must ignore access UIDs.
+            cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
+        }
+
+        // ...and also not to multiple UIDs even including the service UID
+        ncb.setAccessUids(serviceUidSetPlus);
+        mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+        cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
+
+        mCellNetworkAgent.disconnect();
+        cb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+        mCm.unregisterNetworkCallback(cb);
+
+        // Must be unset before touching the transports, because remove and add transport types
+        // check the specifier on the builder immediately, contradicting normal builder semantics
+        // TODO : fix the builder
+        ncb.setNetworkSpecifier(null);
+        ncb.removeTransportType(TRANSPORT_CELLULAR);
+        ncb.addTransportType(TRANSPORT_WIFI);
+        // Wifi does not get to set access UID, even to the correct UID
+        mCm.requestNetwork(new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                .build(), cb);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI,
+                new LinkProperties(), ncb.build());
+        mWiFiNetworkAgent.connect(true);
+        cb.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
+        ncb.setAccessUids(serviceUidSet);
+        mWiFiNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+        cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
+        mCm.unregisterNetworkCallback(cb);
+    }
+
     /**
      * Validate request counts are counted accurately on setProfileNetworkPreference on set/replace.
      */
@@ -15091,4 +15365,153 @@
                 ConnectivityManager.TYPE_NONE, null /* hostAddress */, "com.not.package.owner",
                 null /* callingAttributionTag */));
     }
+
+    @Test
+    public void testUpdateRateLimit_EnableDisable() throws Exception {
+        final LinkProperties wifiLp = new LinkProperties();
+        wifiLp.setInterfaceName(WIFI_IFNAME);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiNetworkAgent.connect(true);
+
+        final LinkProperties cellLp = new LinkProperties();
+        cellLp.setInterfaceName(MOBILE_IFNAME);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellNetworkAgent.connect(false);
+
+        waitForIdle();
+
+        final ArrayTrackRecord<Pair<String, Long>>.ReadHead readHeadWifi =
+                mDeps.mRateLimitHistory.newReadHead();
+        final ArrayTrackRecord<Pair<String, Long>>.ReadHead readHeadCell =
+                mDeps.mRateLimitHistory.newReadHead();
+
+        // set rate limit to 8MBit/s => 1MB/s
+        final int rateLimitInBytesPerSec = 1 * 1000 * 1000;
+        setIngressRateLimit(rateLimitInBytesPerSec);
+
+        assertNotNull(readHeadWifi.poll(TIMEOUT_MS,
+                it -> it.first == wifiLp.getInterfaceName()
+                        && it.second == rateLimitInBytesPerSec));
+        assertNotNull(readHeadCell.poll(TIMEOUT_MS,
+                it -> it.first == cellLp.getInterfaceName()
+                        && it.second == rateLimitInBytesPerSec));
+
+        // disable rate limiting
+        setIngressRateLimit(-1);
+
+        assertNotNull(readHeadWifi.poll(TIMEOUT_MS,
+                it -> it.first == wifiLp.getInterfaceName() && it.second == -1));
+        assertNotNull(readHeadCell.poll(TIMEOUT_MS,
+                it -> it.first == cellLp.getInterfaceName() && it.second == -1));
+    }
+
+    @Test
+    public void testUpdateRateLimit_WhenNewNetworkIsAdded() throws Exception {
+        final LinkProperties wifiLp = new LinkProperties();
+        wifiLp.setInterfaceName(WIFI_IFNAME);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiNetworkAgent.connect(true);
+
+        waitForIdle();
+
+        final ArrayTrackRecord<Pair<String, Long>>.ReadHead readHead =
+                mDeps.mRateLimitHistory.newReadHead();
+
+        // set rate limit to 8MBit/s => 1MB/s
+        final int rateLimitInBytesPerSec = 1 * 1000 * 1000;
+        setIngressRateLimit(rateLimitInBytesPerSec);
+        assertNotNull(readHead.poll(TIMEOUT_MS, it -> it.first == wifiLp.getInterfaceName()
+                && it.second == rateLimitInBytesPerSec));
+
+        final LinkProperties cellLp = new LinkProperties();
+        cellLp.setInterfaceName(MOBILE_IFNAME);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellNetworkAgent.connect(false);
+        assertNotNull(readHead.poll(TIMEOUT_MS, it -> it.first == cellLp.getInterfaceName()
+                && it.second == rateLimitInBytesPerSec));
+    }
+
+    @Test
+    public void testUpdateRateLimit_OnlyAffectsInternetCapableNetworks() throws Exception {
+        final LinkProperties wifiLp = new LinkProperties();
+        wifiLp.setInterfaceName(WIFI_IFNAME);
+
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiNetworkAgent.connectWithoutInternet();
+
+        waitForIdle();
+
+        setIngressRateLimit(1000);
+        setIngressRateLimit(-1);
+
+        final ArrayTrackRecord<Pair<String, Long>>.ReadHead readHeadWifi =
+                mDeps.mRateLimitHistory.newReadHead();
+        assertNull(readHeadWifi.poll(TIMEOUT_MS, it -> it.first == wifiLp.getInterfaceName()));
+    }
+
+    @Test
+    public void testUpdateRateLimit_DisconnectingResetsRateLimit()
+            throws Exception {
+        // Steps:
+        // - connect network
+        // - set rate limit
+        // - disconnect network (interface still exists)
+        // - disable rate limit
+        // - connect network
+        // - ensure network interface is not rate limited
+        final LinkProperties wifiLp = new LinkProperties();
+        wifiLp.setInterfaceName(WIFI_IFNAME);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiNetworkAgent.connect(true);
+        waitForIdle();
+
+        final ArrayTrackRecord<Pair<String, Long>>.ReadHead readHeadWifi =
+                mDeps.mRateLimitHistory.newReadHead();
+
+        int rateLimitInBytesPerSec = 1000;
+        setIngressRateLimit(rateLimitInBytesPerSec);
+        assertNotNull(readHeadWifi.poll(TIMEOUT_MS,
+                it -> it.first == wifiLp.getInterfaceName()
+                        && it.second == rateLimitInBytesPerSec));
+
+        mWiFiNetworkAgent.disconnect();
+        assertNotNull(readHeadWifi.poll(TIMEOUT_MS,
+                it -> it.first == wifiLp.getInterfaceName() && it.second == -1));
+
+        setIngressRateLimit(-1);
+
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiNetworkAgent.connect(true);
+        assertNull(readHeadWifi.poll(TIMEOUT_MS, it -> it.first == wifiLp.getInterfaceName()));
+    }
+
+    @Test
+    public void testUpdateRateLimit_UpdateExistingRateLimit() throws Exception {
+        final LinkProperties wifiLp = new LinkProperties();
+        wifiLp.setInterfaceName(WIFI_IFNAME);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiNetworkAgent.connect(true);
+        waitForIdle();
+
+        final ArrayTrackRecord<Pair<String, Long>>.ReadHead readHeadWifi =
+                mDeps.mRateLimitHistory.newReadHead();
+
+        // update an active ingress rate limit
+        setIngressRateLimit(1000);
+        setIngressRateLimit(2000);
+
+        // verify the following order of execution:
+        // 1. ingress rate limit set to 1000.
+        // 2. ingress rate limit disabled (triggered by updating active rate limit).
+        // 3. ingress rate limit set to 2000.
+        assertNotNull(readHeadWifi.poll(TIMEOUT_MS,
+                it -> it.first == wifiLp.getInterfaceName()
+                        && it.second == 1000));
+        assertNotNull(readHeadWifi.poll(TIMEOUT_MS,
+                it -> it.first == wifiLp.getInterfaceName()
+                        && it.second == -1));
+        assertNotNull(readHeadWifi.poll(TIMEOUT_MS,
+                it -> it.first == wifiLp.getInterfaceName()
+                        && it.second == 2000));
+    }
 }
diff --git a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
index 0c58582..a3b0e7c 100644
--- a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
+++ b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
@@ -16,6 +16,11 @@
 
 package com.android.server;
 
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
 import static android.util.DebugUtils.valueToString;
 
 import static org.junit.Assert.assertEquals;
@@ -277,31 +282,38 @@
         isRestrictedForDozable.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
         isRestrictedForDozable.put(INetd.FIREWALL_RULE_ALLOW, false);
         isRestrictedForDozable.put(INetd.FIREWALL_RULE_DENY, true);
-        expected.put(INetd.FIREWALL_CHAIN_DOZABLE, isRestrictedForDozable);
+        expected.put(FIREWALL_CHAIN_DOZABLE, isRestrictedForDozable);
         // Powersaver chain
         final ArrayMap<Integer, Boolean> isRestrictedForPowerSave = new ArrayMap<>();
         isRestrictedForPowerSave.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
         isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_ALLOW, false);
         isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_DENY, true);
-        expected.put(INetd.FIREWALL_CHAIN_POWERSAVE, isRestrictedForPowerSave);
+        expected.put(FIREWALL_CHAIN_POWERSAVE, isRestrictedForPowerSave);
         // Standby chain
         final ArrayMap<Integer, Boolean> isRestrictedForStandby = new ArrayMap<>();
         isRestrictedForStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, false);
         isRestrictedForStandby.put(INetd.FIREWALL_RULE_ALLOW, false);
         isRestrictedForStandby.put(INetd.FIREWALL_RULE_DENY, true);
-        expected.put(INetd.FIREWALL_CHAIN_STANDBY, isRestrictedForStandby);
+        expected.put(FIREWALL_CHAIN_STANDBY, isRestrictedForStandby);
         // Restricted mode chain
         final ArrayMap<Integer, Boolean> isRestrictedForRestrictedMode = new ArrayMap<>();
         isRestrictedForRestrictedMode.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
         isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_ALLOW, false);
         isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_DENY, true);
-        expected.put(INetd.FIREWALL_CHAIN_RESTRICTED, isRestrictedForRestrictedMode);
+        expected.put(FIREWALL_CHAIN_RESTRICTED, isRestrictedForRestrictedMode);
+        // Low Power Standby chain
+        final ArrayMap<Integer, Boolean> isRestrictedForLowPowerStandby = new ArrayMap<>();
+        isRestrictedForLowPowerStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+        isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_LOW_POWER_STANDBY, isRestrictedForLowPowerStandby);
 
         final int[] chains = {
-                INetd.FIREWALL_CHAIN_STANDBY,
-                INetd.FIREWALL_CHAIN_POWERSAVE,
-                INetd.FIREWALL_CHAIN_DOZABLE,
-                INetd.FIREWALL_CHAIN_RESTRICTED
+                FIREWALL_CHAIN_STANDBY,
+                FIREWALL_CHAIN_POWERSAVE,
+                FIREWALL_CHAIN_DOZABLE,
+                FIREWALL_CHAIN_RESTRICTED,
+                FIREWALL_CHAIN_LOW_POWER_STANDBY
         };
         final int[] states = {
                 INetd.FIREWALL_RULE_ALLOW,
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
new file mode 100644
index 0000000..553cb83
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import com.android.networkstack.apishim.TelephonyManagerShimImpl;
+import com.android.networkstack.apishim.common.TelephonyManagerShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Tests for CarrierPrivilegeAuthenticatorTest.
+ *
+ * Build, install and run with:
+ *  runtest frameworks-net -c com.android.server.connectivity.CarrierPrivilegeAuthenticatorTest
+ */
+@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;
+
+    @NonNull private final Context mContext;
+    @NonNull private final TelephonyManager mTelephonyManager;
+    @NonNull private final TelephonyManagerShimImpl mTelephonyManagerShim;
+    @NonNull private final PackageManager mPackageManager;
+    @NonNull private TestCarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
+    private final int mCarrierConfigPkgUid = 12345;
+    private final String mTestPkg = "com.android.server.connectivity.test";
+
+    public class TestCarrierPrivilegeAuthenticator extends CarrierPrivilegeAuthenticator {
+        TestCarrierPrivilegeAuthenticator(@NonNull final Context c,
+                @NonNull final TelephonyManager t) {
+            super(c, t, mTelephonyManagerShim);
+        }
+        @Override
+        protected int getSlotIndex(int subId) {
+            if (SubscriptionManager.DEFAULT_SUBSCRIPTION_ID == subId) return TEST_SUBSCRIPTION_ID;
+            return subId;
+        }
+    }
+
+    public CarrierPrivilegeAuthenticatorTest() {
+        mContext = mock(Context.class);
+        mTelephonyManager = mock(TelephonyManager.class);
+        mTelephonyManagerShim = mock(TelephonyManagerShimImpl.class);
+        mPackageManager = mock(PackageManager.class);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        doReturn(SUBSCRIPTION_COUNT).when(mTelephonyManager).getActiveModemCount();
+        doReturn(mTestPkg).when(mTelephonyManagerShim)
+                .getCarrierServicePackageNameForLogicalSlot(anyInt());
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.uid = mCarrierConfigPkgUid;
+        doReturn(applicationInfo).when(mPackageManager)
+                .getApplicationInfo(eq(mTestPkg), anyInt());
+        mCarrierPrivilegeAuthenticator =
+                new TestCarrierPrivilegeAuthenticator(mContext, mTelephonyManager);
+    }
+
+    private IntentFilter getIntentFilter() {
+        final ArgumentCaptor<IntentFilter> captor = ArgumentCaptor.forClass(IntentFilter.class);
+        verify(mContext).registerReceiver(any(), captor.capture(), any(), any(), anyInt());
+        return captor.getValue();
+    }
+
+    private List<TelephonyManagerShim.CarrierPrivilegesListenerShim>
+            getCarrierPrivilegesListeners() {
+        final ArgumentCaptor<TelephonyManagerShim.CarrierPrivilegesListenerShim> captor =
+                ArgumentCaptor.forClass(TelephonyManagerShim.CarrierPrivilegesListenerShim.class);
+        try {
+            verify(mTelephonyManagerShim, atLeastOnce())
+                    .addCarrierPrivilegesListener(anyInt(), any(), captor.capture());
+        } catch (UnsupportedApiLevelException e) {
+        }
+        return captor.getAllValues();
+    }
+
+    private Intent buildTestMultiSimConfigBroadcastIntent() {
+        final Intent intent = new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED);
+        return intent;
+    }
+    @Test
+    public void testConstructor() throws Exception {
+        verify(mContext).registerReceiver(
+                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));
+
+        verify(mTelephonyManagerShim, times(2))
+                .addCarrierPrivilegesListener(anyInt(), any(), any());
+        verify(mTelephonyManagerShim)
+                .addCarrierPrivilegesListener(eq(0), any(), any());
+        verify(mTelephonyManagerShim)
+                .addCarrierPrivilegesListener(eq(1), any(), any());
+        assertEquals(2, getCarrierPrivilegesListeners().size());
+
+        final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+                new TelephonyNetworkSpecifier(0);
+        final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+        networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
+        networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier);
+
+        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid + 1, networkRequestBuilder.build().networkCapabilities));
+    }
+
+    @Test
+    public void testMultiSimConfigChanged() throws Exception {
+        doReturn(1).when(mTelephonyManager).getActiveModemCount();
+        final List<TelephonyManagerShim.CarrierPrivilegesListenerShim> carrierPrivilegesListeners =
+                getCarrierPrivilegesListeners();
+
+        mCarrierPrivilegeAuthenticator.onReceive(
+                mContext, buildTestMultiSimConfigBroadcastIntent());
+        for (TelephonyManagerShim.CarrierPrivilegesListenerShim carrierPrivilegesListener
+                : carrierPrivilegesListeners) {
+            verify(mTelephonyManagerShim)
+                    .removeCarrierPrivilegesListener(eq(carrierPrivilegesListener));
+        }
+
+        // Expect a new CarrierPrivilegesListener to have been registered for slot 0, and none other
+        // (2 previously registered during startup, for slots 0 & 1)
+        verify(mTelephonyManagerShim, times(3))
+                .addCarrierPrivilegesListener(anyInt(), any(), any());
+        verify(mTelephonyManagerShim, times(2))
+                .addCarrierPrivilegesListener(eq(0), any(), any());
+
+        final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+                new TelephonyNetworkSpecifier(0);
+        final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+        networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
+        networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier);
+        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid + 1, networkRequestBuilder.build().networkCapabilities));
+    }
+
+    @Test
+    public void testOnCarrierPrivilegesChanged() throws Exception {
+        final TelephonyManagerShim.CarrierPrivilegesListenerShim listener =
+                getCarrierPrivilegesListeners().get(0);
+
+        final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+                new TelephonyNetworkSpecifier(0);
+        final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+        networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
+        networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier);
+
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.uid = mCarrierConfigPkgUid + 1;
+        doReturn(applicationInfo).when(mPackageManager)
+                .getApplicationInfo(eq(mTestPkg), anyInt());
+        listener.onCarrierPrivilegesChanged(Collections.emptyList(), new int[] {});
+
+        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid + 1, networkRequestBuilder.build().networkCapabilities));
+    }
+
+    @Test
+    public void testDefaultSubscription() throws Exception {
+        final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+        networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
+        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+
+        networkRequestBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(0));
+        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+
+        // The builder for NetworkRequest doesn't allow removing the transport as long as a
+        // specifier is set, so unset it first. TODO : fix the builder
+        networkRequestBuilder.setNetworkSpecifier((NetworkSpecifier) null);
+        networkRequestBuilder.removeTransportType(TRANSPORT_CELLULAR);
+        networkRequestBuilder.addTransportType(TRANSPORT_WIFI);
+        networkRequestBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(0));
+        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index 99ef80b..6590543 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -89,6 +89,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.net.module.util.CollectionUtils;
+import com.android.server.BpfNetMaps;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
@@ -146,9 +147,11 @@
     @Mock private UserManager mUserManager;
     @Mock private PermissionMonitor.Dependencies mDeps;
     @Mock private SystemConfigManager mSystemConfigManager;
+    @Mock private BpfNetMaps mBpfNetMaps;
 
     private PermissionMonitor mPermissionMonitor;
     private NetdMonitor mNetdMonitor;
+    private BpfMapMonitor mBpfMapMonitor;
 
     @Before
     public void setUp() throws Exception {
@@ -177,8 +180,9 @@
         // by default.
         doReturn(VERSION_Q).when(mDeps).getDeviceFirstSdkInt();
 
-        mPermissionMonitor = new PermissionMonitor(mContext, mNetdService, mDeps);
+        mPermissionMonitor = new PermissionMonitor(mContext, mNetdService, mBpfNetMaps, mDeps);
         mNetdMonitor = new NetdMonitor(mNetdService);
+        mBpfMapMonitor = new BpfMapMonitor(mBpfNetMaps);
 
         doReturn(List.of()).when(mPackageManager).getInstalledPackagesAsUser(anyInt(), anyInt());
     }
@@ -511,9 +515,37 @@
         assertBackgroundPermission(true, MOCK_PACKAGE2, MOCK_UID12, NETWORK_STACK);
     }
 
+    private class BpfMapMonitor {
+        private final SparseIntArray mAppIdsTrafficPermission = new SparseIntArray();
+        private static final int DOES_NOT_EXIST = -2;
+
+        BpfMapMonitor(BpfNetMaps mockBpfmap) throws Exception {
+            // Add hook to verify and track result of trafficSetNetPerm.
+            doAnswer((InvocationOnMock invocation) -> {
+                final Object[] args = invocation.getArguments();
+                final int permission = (int) args[0];
+                for (final int appId : (int[]) args[1]) {
+                    mAppIdsTrafficPermission.put(appId, permission);
+                }
+                return null;
+            }).when(mockBpfmap).setNetPermForUids(anyInt(), any(int[].class));
+        }
+
+        public void expectTrafficPerm(int permission, int... appIds) {
+            for (final int appId : appIds) {
+                if (mAppIdsTrafficPermission.get(appId, DOES_NOT_EXIST) == DOES_NOT_EXIST) {
+                    fail("appId " + appId + " does not exist.");
+                }
+                if (mAppIdsTrafficPermission.get(appId) != permission) {
+                    fail("appId " + appId + " has wrong permission: "
+                            + mAppIdsTrafficPermission.get(appId));
+                }
+            }
+        }
+    }
+
     private class NetdMonitor {
         private final SparseIntArray mUidsNetworkPermission = new SparseIntArray();
-        private final SparseIntArray mAppIdsTrafficPermission = new SparseIntArray();
         private static final int DOES_NOT_EXIST = -2;
 
         NetdMonitor(INetd mockNetd) throws Exception {
@@ -545,16 +577,6 @@
                 }
                 return null;
             }).when(mockNetd).networkClearPermissionForUser(any(int[].class));
-
-            // Add hook to verify and track result of trafficSetNetPerm.
-            doAnswer((InvocationOnMock invocation) -> {
-                final Object[] args = invocation.getArguments();
-                final int permission = (int) args[0];
-                for (final int appId : (int[]) args[1]) {
-                    mAppIdsTrafficPermission.put(appId, permission);
-                }
-                return null;
-            }).when(mockNetd).trafficSetNetPermForUids(anyInt(), any(int[].class));
         }
 
         public void expectNetworkPerm(int permission, UserHandle[] users, int... appIds) {
@@ -581,18 +603,6 @@
                 }
             }
         }
-
-        public void expectTrafficPerm(int permission, int... appIds) {
-            for (final int appId : appIds) {
-                if (mAppIdsTrafficPermission.get(appId, DOES_NOT_EXIST) == DOES_NOT_EXIST) {
-                    fail("appId " + appId + " does not exist.");
-                }
-                if (mAppIdsTrafficPermission.get(appId) != permission) {
-                    fail("appId " + appId + " has wrong permission: "
-                            + mAppIdsTrafficPermission.get(appId));
-                }
-            }
-        }
     }
 
     @Test
@@ -702,30 +712,30 @@
 
         // When VPN is connected, expect a rule to be set up for user app MOCK_UID11
         mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange1, VPN_UID);
-        verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
+        verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
 
-        reset(mNetdService);
+        reset(mBpfNetMaps);
 
         // When MOCK_UID11 package is uninstalled and reinstalled, expect Netd to be updated
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
-        verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[]{MOCK_UID11}));
+        verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[]{MOCK_UID11}));
         mPermissionMonitor.onPackageAdded(MOCK_PACKAGE1, MOCK_UID11);
-        verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
+        verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
 
-        reset(mNetdService);
+        reset(mBpfNetMaps);
 
         // During VPN uid update (vpnRange1 -> vpnRange2), ConnectivityService first deletes the
         // old UID rules then adds the new ones. Expect netd to be updated
         mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange1, VPN_UID);
-        verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID11}));
+        verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[] {MOCK_UID11}));
         mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange2, VPN_UID);
-        verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID12}));
+        verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID12}));
 
-        reset(mNetdService);
+        reset(mBpfNetMaps);
 
         // When VPN is disconnected, expect rules to be torn down
         mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange2, VPN_UID);
-        verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID12}));
+        verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[] {MOCK_UID12}));
         assertNull(mPermissionMonitor.getVpnUidRanges("tun0"));
     }
 
@@ -744,13 +754,13 @@
 
         // Newly-installed package should have uid rules added
         addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_APPID1);
-        verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
-        verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID21}));
+        verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID11}));
+        verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), aryEq(new int[]{MOCK_UID21}));
 
         // Removed package should have its uid rules removed
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
-        verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[]{MOCK_UID11}));
-        verify(mNetdService, never()).firewallRemoveUidInterfaceRules(aryEq(new int[]{MOCK_UID21}));
+        verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[]{MOCK_UID11}));
+        verify(mBpfNetMaps, never()).removeUidInterfaceRules(aryEq(new int[]{MOCK_UID21}));
     }
 
 
@@ -784,91 +794,91 @@
         // Send the permission information to netd, expect permission updated.
         mPermissionMonitor.sendAppIdsTrafficPermission(netdPermissionsAppIds);
 
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID2);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, SYSTEM_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, SYSTEM_APPID2);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID2);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, SYSTEM_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, SYSTEM_APPID2);
 
         // Update permission of MOCK_APPID1, expect new permission show up.
         mPermissionMonitor.sendPackagePermissionsForAppId(MOCK_APPID1, PERMISSION_TRAFFIC_ALL);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
 
         // Change permissions of SYSTEM_APPID2, expect new permission show up and old permission
         // revoked.
         mPermissionMonitor.sendPackagePermissionsForAppId(SYSTEM_APPID2, PERMISSION_INTERNET);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, SYSTEM_APPID2);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, SYSTEM_APPID2);
 
         // Revoke permission from SYSTEM_APPID1, expect no permission stored.
         mPermissionMonitor.sendPackagePermissionsForAppId(SYSTEM_APPID1, PERMISSION_NONE);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_NONE, SYSTEM_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_NONE, SYSTEM_APPID1);
     }
 
     @Test
     public void testPackageInstall() throws Exception {
         addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
 
         addPackage(MOCK_PACKAGE2, MOCK_UID12, INTERNET);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID2);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID2);
     }
 
     @Test
     public void testPackageInstallSharedUid() throws Exception {
         addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
 
         // Install another package with the same uid and no permissions should not cause the app id
         // to lose permissions.
         addPackage(MOCK_PACKAGE2, MOCK_UID11);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
     }
 
     @Test
     public void testPackageUninstallBasic() throws Exception {
         addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
 
         when(mPackageManager.getPackagesForUid(MOCK_UID11)).thenReturn(new String[]{});
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
     }
 
     @Test
     public void testPackageRemoveThenAdd() throws Exception {
         addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
 
         when(mPackageManager.getPackagesForUid(MOCK_UID11)).thenReturn(new String[]{});
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
 
         addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
     }
 
     @Test
     public void testPackageUpdate() throws Exception {
         addPackage(MOCK_PACKAGE1, MOCK_UID11);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1);
 
         addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
     }
 
     @Test
     public void testPackageUninstallWithMultiplePackages() throws Exception {
         addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
 
         // Install another package with the same uid but different permissions.
         addPackage(MOCK_PACKAGE2, MOCK_UID11, INTERNET);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_UID11);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_UID11);
 
         // Uninstall MOCK_PACKAGE1 and expect only INTERNET permission left.
         when(mPackageManager.getPackagesForUid(eq(MOCK_UID11)))
                 .thenReturn(new String[]{MOCK_PACKAGE2});
         mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
     }
 
     @Test
@@ -876,7 +886,8 @@
         // Use the real context as this test must ensure the *real* system package holds the
         // necessary permission.
         final Context realContext = InstrumentationRegistry.getContext();
-        final PermissionMonitor monitor = new PermissionMonitor(realContext, mNetdService);
+        final PermissionMonitor monitor = new PermissionMonitor(realContext, mNetdService,
+                mBpfNetMaps);
         final PackageManager manager = realContext.getPackageManager();
         final PackageInfo systemInfo = manager.getPackageInfo(REAL_SYSTEM_PACKAGE_NAME,
                 GET_PERMISSIONS | MATCH_ANY_USER);
@@ -891,8 +902,8 @@
                 .thenReturn(new int[]{ MOCK_UID12 });
 
         mPermissionMonitor.startMonitoring();
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID2);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID2);
     }
 
     private BroadcastReceiver expectBroadcastReceiver(String... actions) {
@@ -923,7 +934,7 @@
         buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID11, INTERNET,
                 UPDATE_DEVICE_STATS);
         receiver.onReceive(mContext, addedIntent);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
 
         // Verify receiving PACKAGE_REMOVED intent.
         when(mPackageManager.getPackagesForUid(MOCK_UID11)).thenReturn(new String[]{});
@@ -931,7 +942,7 @@
                 Uri.fromParts("package", MOCK_PACKAGE1, null /* fragment */));
         removedIntent.putExtra(Intent.EXTRA_UID, MOCK_UID11);
         receiver.onReceive(mContext, removedIntent);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, MOCK_APPID1);
     }
 
     private ContentObserver expectRegisterContentObserver(Uri expectedUri) {
@@ -1070,7 +1081,7 @@
                 .when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
         mPermissionMonitor.startMonitoring();
         mNetdMonitor.expectNoNetworkPerm(new UserHandle[]{MOCK_USER1}, MOCK_APPID1, MOCK_APPID2);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1, MOCK_APPID2);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1, MOCK_APPID2);
 
         final BroadcastReceiver receiver = expectBroadcastReceiver(
                 Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
@@ -1087,8 +1098,8 @@
                 MOCK_APPID1);
         mNetdMonitor.expectNetworkPerm(PERMISSION_NETWORK, new UserHandle[]{MOCK_USER1},
                 MOCK_APPID2);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID2);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID2);
     }
 
     @Test
@@ -1114,8 +1125,8 @@
                 MOCK_APPID1);
         mNetdMonitor.expectNetworkPerm(PERMISSION_NETWORK, new UserHandle[]{MOCK_USER1},
                 MOCK_APPID2);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID2);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID2);
     }
 
     @Test
@@ -1128,7 +1139,7 @@
                 .when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
         mPermissionMonitor.startMonitoring();
         mNetdMonitor.expectNoNetworkPerm(new UserHandle[]{MOCK_USER1}, MOCK_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_NONE, MOCK_APPID1);
 
         final BroadcastReceiver receiver = expectBroadcastReceiver(
                 Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
@@ -1140,7 +1151,7 @@
         receiver.onReceive(mContext, externalIntent);
         mNetdMonitor.expectNetworkPerm(PERMISSION_NETWORK, new UserHandle[]{MOCK_USER1},
                 MOCK_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_UPDATE_DEVICE_STATS, MOCK_APPID1);
     }
 
     @Test
@@ -1155,7 +1166,7 @@
         mPermissionMonitor.startMonitoring();
         mNetdMonitor.expectNetworkPerm(PERMISSION_NETWORK, new UserHandle[]{MOCK_USER1},
                 MOCK_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_INTERNET, MOCK_APPID1);
 
         final BroadcastReceiver receiver = expectBroadcastReceiver(
                 Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
@@ -1169,7 +1180,7 @@
         receiver.onReceive(mContext, externalIntent);
         mNetdMonitor.expectNetworkPerm(PERMISSION_SYSTEM, new UserHandle[]{MOCK_USER1},
                 MOCK_APPID1);
-        mNetdMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
+        mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 0e2c293..6872f80 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -28,7 +28,7 @@
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 
-import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
+import static com.android.server.net.NetworkStatsFactory.kernelToTag;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 45f033c..76c0c38 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -198,6 +198,7 @@
     @Mock
     private LocationPermissionChecker mLocationPermissionChecker;
     private @Mock IBpfMap<U32, U8> mUidCounterSetMap;
+    private @Mock NetworkStatsService.TagStatsDeleter mTagStatsDeleter;
 
     private NetworkStatsService mService;
     private INetworkStatsSession mSession;
@@ -358,6 +359,11 @@
             public IBpfMap<U32, U8> getUidCounterSetMap() {
                 return mUidCounterSetMap;
             }
+
+            @Override
+            public NetworkStatsService.TagStatsDeleter getTagStatsDeleter() {
+                return mTagStatsDeleter;
+            }
         };
     }
 
@@ -696,8 +702,10 @@
         final Intent intent = new Intent(ACTION_UID_REMOVED);
         intent.putExtra(EXTRA_UID, UID_BLUE);
         mServiceContext.sendBroadcast(intent);
+        verify(mTagStatsDeleter).deleteTagData(UID_BLUE);
         intent.putExtra(EXTRA_UID, UID_RED);
         mServiceContext.sendBroadcast(intent);
+        verify(mTagStatsDeleter).deleteTagData(UID_RED);
 
         // existing uid and total should remain unchanged; but removed UID
         // should be gone completely.
diff --git a/tests/unit/jni/Android.bp b/tests/unit/jni/Android.bp
index 04ba98f..616da81 100644
--- a/tests/unit/jni/Android.bp
+++ b/tests/unit/jni/Android.bp
@@ -21,7 +21,6 @@
     ],
 
     shared_libs: [
-        "libbpf_android",
         "liblog",
         "libnativehelper",
         "libnetdutils",