Add basic functionality tests for IPsec tunnel mode

This change adds test for creation and address additions of IPsec tunnel
mode.

Bug: 72458318
Test: Ran on aosp_taimen-eng
Change-Id: If9d7e5ef35d37242d452eb7386cd5f5b80f6351f
diff --git a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
index 7132ecf..35d0f48 100644
--- a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertArrayEquals;
 
 import android.content.Context;
+import android.net.ConnectivityManager;
 import android.net.IpSecAlgorithm;
 import android.net.IpSecManager;
 import android.net.IpSecTransform;
@@ -66,11 +67,13 @@
     protected static final byte[] AUTH_KEY = getKey(256);
     protected static final byte[] CRYPT_KEY = getKey(256);
 
+    protected ConnectivityManager mCM;
     protected IpSecManager mISM;
 
     protected void setUp() throws Exception {
         super.setUp();
         mISM = (IpSecManager) getContext().getSystemService(Context.IPSEC_SERVICE);
+        mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
     }
 
     protected static byte[] getKey(int bitLength) {
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index a18b2f0..3387064 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -21,8 +21,6 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertTrue;
 
-import android.content.Context;
-import android.net.ConnectivityManager;
 import android.net.IpSecAlgorithm;
 import android.net.IpSecManager;
 import android.net.IpSecTransform;
@@ -37,25 +35,15 @@
 import java.net.DatagramSocket;
 import java.net.Inet6Address;
 import java.net.InetAddress;
-import java.net.UnknownHostException;
 import java.util.Arrays;
 
 public class IpSecManagerTest extends IpSecBaseTest {
 
     private static final String TAG = IpSecManagerTest.class.getSimpleName();
 
-    private ConnectivityManager mCM;
-
-    private static InetAddress IpAddress(String addrString) {
-        try {
-            return InetAddress.getByName(addrString);
-        } catch (UnknownHostException e) {
-            throw new IllegalArgumentException("Invalid IP address: " + e);
-        }
-    }
-
-    private static final InetAddress GOOGLE_DNS_4 = IpAddress("8.8.8.8");
-    private static final InetAddress GOOGLE_DNS_6 = IpAddress("2001:4860:4860::8888");
+    private static final InetAddress GOOGLE_DNS_4 = InetAddress.parseNumericAddress("8.8.8.8");
+    private static final InetAddress GOOGLE_DNS_6 =
+            InetAddress.parseNumericAddress("2001:4860:4860::8888");
 
     private static final InetAddress[] GOOGLE_DNS_LIST =
             new InetAddress[] {GOOGLE_DNS_4, GOOGLE_DNS_6};
@@ -78,7 +66,6 @@
 
     protected void setUp() throws Exception {
         super.setUp();
-        mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
     }
 
     /*
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
new file mode 100644
index 0000000..5c80e33
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2018 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.IpSecAlgorithm;
+import android.net.IpSecManager;
+import android.net.IpSecTransform;
+import android.net.Network;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+
+public class IpSecManagerTunnelTest extends IpSecBaseTest {
+
+    private static final String TAG = IpSecManagerTunnelTest.class.getSimpleName();
+    private static final int IP4_PREFIX_LEN = 24;
+    private static final int IP6_PREFIX_LEN = 48;
+    private static final InetAddress OUTER_ADDR4 = InetAddress.parseNumericAddress("192.0.2.0");
+    private static final InetAddress OUTER_ADDR6 =
+            InetAddress.parseNumericAddress("2001:db8:f00d::1");
+    private static final InetAddress INNER_ADDR4 = InetAddress.parseNumericAddress("10.0.0.1");
+    private static final InetAddress INNER_ADDR6 =
+            InetAddress.parseNumericAddress("2001:db8:d00d::1");
+
+    private Network mUnderlyingNetwork;
+    private Network mIpSecNetwork;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        setAppop(true);
+    }
+
+    protected void tearDown() {
+        setAppop(false);
+    }
+
+    private void setAppop(boolean allow) {
+        // Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted by the
+        // telephony framework, and the only permission that is sufficient is NETWORK_STACK. So we
+        // shell out the appop manager, to give us the right appop permissions.
+        String cmd =
+                "appops set "
+                        + mContext.getPackageName()
+                        + " MANAGE_IPSEC_TUNNELS "
+                + (allow ? "allow" : "deny");
+        SystemUtil.runShellCommand(cmd);
+    }
+
+    private void checkTunnel(InetAddress inner, InetAddress outer, boolean useEncap)
+            throws Exception {
+        int innerPrefixLen = inner instanceof Inet6Address ? IP6_PREFIX_LEN : IP4_PREFIX_LEN;
+
+        try (IpSecManager.SecurityParameterIndex spi = mISM.allocateSecurityParameterIndex(outer);
+                IpSecManager.IpSecTunnelInterface tunnelIntf =
+                        mISM.createIpSecTunnelInterface(outer, outer, mCM.getActiveNetwork());
+                IpSecManager.UdpEncapsulationSocket encapSocket =
+                        mISM.openUdpEncapsulationSocket()) {
+
+            IpSecTransform.Builder transformBuilder = new IpSecTransform.Builder(mContext);
+            transformBuilder.setEncryption(
+                    new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY));
+            transformBuilder.setAuthentication(
+                    new IpSecAlgorithm(
+                            IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4));
+
+            if (useEncap) {
+                transformBuilder.setIpv4Encapsulation(encapSocket, encapSocket.getPort());
+            }
+
+            // Check transform application
+            try (IpSecTransform transform = transformBuilder.buildTunnelModeTransform(outer, spi)) {
+                mISM.applyTunnelModeTransform(tunnelIntf, IpSecManager.DIRECTION_IN, transform);
+                mISM.applyTunnelModeTransform(tunnelIntf, IpSecManager.DIRECTION_OUT, transform);
+
+                // TODO: Test to ensure that send/receive works with these transforms.
+            }
+
+            // Check interface was created
+            NetworkInterface netIntf = NetworkInterface.getByName(tunnelIntf.getInterfaceName());
+            assertTrue(netIntf.isPointToPoint());
+            assertNotNull(netIntf);
+
+            // Add addresses and check
+            tunnelIntf.addAddress(inner, innerPrefixLen);
+            for (InterfaceAddress intfAddr : netIntf.getInterfaceAddresses()) {
+                assertEquals(intfAddr.getAddress(), inner);
+                assertEquals(intfAddr.getNetworkPrefixLength(), innerPrefixLen);
+            }
+
+            // Remove addresses and check
+            tunnelIntf.removeAddress(inner, innerPrefixLen);
+            assertTrue(netIntf.getInterfaceAddresses().isEmpty());
+
+            // Check interface was cleaned up
+            tunnelIntf.close();
+            netIntf = NetworkInterface.getByName(tunnelIntf.getInterfaceName());
+            assertNull(netIntf);
+        }
+    }
+
+    /*
+     * Create, add and remove addresses, then teardown tunnel
+     */
+    public void testTunnelV4InV4() throws Exception {
+        checkTunnel(INNER_ADDR4, OUTER_ADDR4, false);
+    }
+
+    public void testTunnelV4InV4UdpEncap() throws Exception {
+        checkTunnel(INNER_ADDR4, OUTER_ADDR4, true);
+    }
+
+    public void testTunnelV4InV6() throws Exception {
+        checkTunnel(INNER_ADDR4, OUTER_ADDR6, false);
+    }
+
+    public void testTunnelV6InV4() throws Exception {
+        checkTunnel(INNER_ADDR6, OUTER_ADDR4, false);
+    }
+
+    public void testTunnelV6InV4UdpEncap() throws Exception {
+        checkTunnel(INNER_ADDR6, OUTER_ADDR4, true);
+    }
+
+    public void testTunnelV6InV6() throws Exception {
+        checkTunnel(INNER_ADDR6, OUTER_ADDR6, false);
+    }
+}