Add IPsec checks for IPSEC_TUNNEL feature

This patch adds checks to ensure that the IPSEC_TUNNEL feature flag is
enabled.

Bug: 117183273
Test: Compiles & tests passing
Change-Id: I2699dda29e1eed139bc6fd1b70071e5ab33cad88
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 1145d5b..61fa04a 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -19,11 +19,13 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -749,6 +751,7 @@
          * @hide
          */
         @SystemApi
+        @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
         @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
         public void addAddress(@NonNull InetAddress address, int prefixLen) throws IOException {
             try {
@@ -771,6 +774,7 @@
          * @hide
          */
         @SystemApi
+        @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
         @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
         public void removeAddress(@NonNull InetAddress address, int prefixLen) throws IOException {
             try {
@@ -886,6 +890,7 @@
      */
     @SystemApi
     @NonNull
+    @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
     @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
     public IpSecTunnelInterface createIpSecTunnelInterface(@NonNull InetAddress localAddress,
             @NonNull InetAddress remoteAddress, @NonNull Network underlyingNetwork)
@@ -916,6 +921,7 @@
      * @hide
      */
     @SystemApi
+    @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
     @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
     public void applyTunnelModeTransform(@NonNull IpSecTunnelInterface tunnel,
             @PolicyDirection int direction, @NonNull IpSecTransform transform) throws IOException {
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index e519fdf..36111f2 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -21,9 +21,11 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -483,6 +485,7 @@
          */
         @SystemApi
         @NonNull
+        @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
         @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
         public IpSecTransform buildTunnelModeTransform(
                 @NonNull InetAddress sourceAddress,
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 2055b64..fe22dcd 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -30,6 +30,7 @@
 import android.annotation.NonNull;
 import android.app.AppOpsManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.net.IIpSecService;
 import android.net.INetd;
 import android.net.IpSecAlgorithm;
@@ -1276,7 +1277,7 @@
     public synchronized IpSecTunnelInterfaceResponse createTunnelInterface(
             String localAddr, String remoteAddr, Network underlyingNetwork, IBinder binder,
             String callingPackage) {
-        enforceTunnelPermissions(callingPackage);
+        enforceTunnelFeatureAndPermissions(callingPackage);
         checkNotNull(binder, "Null Binder passed to createTunnelInterface");
         checkNotNull(underlyingNetwork, "No underlying network was specified");
         checkInetAddress(localAddr);
@@ -1362,7 +1363,7 @@
     @Override
     public synchronized void addAddressToTunnelInterface(
             int tunnelResourceId, LinkAddress localAddr, String callingPackage) {
-        enforceTunnelPermissions(callingPackage);
+        enforceTunnelFeatureAndPermissions(callingPackage);
         UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
 
         // Get tunnelInterface record; if no such interface is found, will throw
@@ -1391,7 +1392,7 @@
     @Override
     public synchronized void removeAddressFromTunnelInterface(
             int tunnelResourceId, LinkAddress localAddr, String callingPackage) {
-        enforceTunnelPermissions(callingPackage);
+        enforceTunnelFeatureAndPermissions(callingPackage);
 
         UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
         // Get tunnelInterface record; if no such interface is found, will throw
@@ -1420,7 +1421,7 @@
     @Override
     public synchronized void deleteTunnelInterface(
             int resourceId, String callingPackage) throws RemoteException {
-        enforceTunnelPermissions(callingPackage);
+        enforceTunnelFeatureAndPermissions(callingPackage);
         UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
         releaseResource(userRecord.mTunnelInterfaceRecords, resourceId);
     }
@@ -1549,7 +1550,12 @@
 
     private static final String TUNNEL_OP = AppOpsManager.OPSTR_MANAGE_IPSEC_TUNNELS;
 
-    private void enforceTunnelPermissions(String callingPackage) {
+    private void enforceTunnelFeatureAndPermissions(String callingPackage) {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)) {
+            throw new UnsupportedOperationException(
+                    "IPsec Tunnel Mode requires PackageManager.FEATURE_IPSEC_TUNNELS");
+        }
+
         checkNotNull(callingPackage, "Null calling package cannot create IpSec tunnels");
         switch (getAppOpsManager().noteOp(TUNNEL_OP, Binder.getCallingUid(), callingPackage)) {
             case AppOpsManager.MODE_DEFAULT:
@@ -1621,7 +1627,7 @@
             IpSecConfig c, IBinder binder, String callingPackage) throws RemoteException {
         checkNotNull(c);
         if (c.getMode() == IpSecTransform.MODE_TUNNEL) {
-            enforceTunnelPermissions(callingPackage);
+            enforceTunnelFeatureAndPermissions(callingPackage);
         }
         checkIpSecConfig(c);
         checkNotNull(binder, "Null Binder passed to createTransform");
@@ -1729,7 +1735,7 @@
     public synchronized void applyTunnelModeTransform(
             int tunnelResourceId, int direction,
             int transformResourceId, String callingPackage) throws RemoteException {
-        enforceTunnelPermissions(callingPackage);
+        enforceTunnelFeatureAndPermissions(callingPackage);
         checkDirection(direction);
 
         int callingUid = Binder.getCallingUid();
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 7c40adf..71b72b8 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -32,6 +32,7 @@
 
 import android.app.AppOpsManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.net.INetd;
 import android.net.IpSecAlgorithm;
 import android.net.IpSecConfig;
@@ -57,6 +58,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+import java.net.Inet4Address;
 import java.net.Socket;
 import java.util.Arrays;
 import java.util.Collection;
@@ -119,6 +121,11 @@
         }
 
         @Override
+        public PackageManager getPackageManager() {
+            return mMockPkgMgr;
+        }
+
+        @Override
         public void enforceCallingOrSelfPermission(String permission, String message) {
             if (permission == android.Manifest.permission.MANAGE_IPSEC_TUNNELS) {
                 return;
@@ -128,6 +135,7 @@
     };
 
     INetd mMockNetd;
+    PackageManager mMockPkgMgr;
     IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
     IpSecService mIpSecService;
     Network fakeNetwork = new Network(0xAB);
@@ -152,11 +160,16 @@
     @Before
     public void setUp() throws Exception {
         mMockNetd = mock(INetd.class);
+        mMockPkgMgr = mock(PackageManager.class);
         mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class);
         mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig);
 
         // Injecting mock netd
         when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd);
+
+        // PackageManager should always return true (feature flag tests in IpSecServiceTest)
+        when(mMockPkgMgr.hasSystemFeature(anyString())).thenReturn(true);
+
         // A package granted the AppOp for MANAGE_IPSEC_TUNNELS will be MODE_ALLOWED.
         when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("blessedPackage")))
             .thenReturn(AppOpsManager.MODE_ALLOWED);
@@ -709,4 +722,18 @@
         } catch (SecurityException expected) {
         }
     }
+
+    @Test
+    public void testFeatureFlagVerification() throws Exception {
+        when(mMockPkgMgr.hasSystemFeature(eq(PackageManager.FEATURE_IPSEC_TUNNELS)))
+                .thenReturn(false);
+
+        try {
+            String addr = Inet4Address.getLoopbackAddress().getHostAddress();
+            mIpSecService.createTunnelInterface(
+                    addr, addr, new Network(0), new Binder(), "blessedPackage");
+            fail("Expected UnsupportedOperationException for disabled feature");
+        } catch (UnsupportedOperationException expected) {
+        }
+    }
 }