Move IpSec associated files to f/b/packages/ConnectivityT

IpSecService is going to be moved into Connectivity mainline module.
Move all ipsec associated files to packages/ConnectivityT so that
it can be easily migrate these files to connectivity module after
clearing the hidden API usages.

Bug: 204153604
Test: build pass
      FrameworksNetTests
      CtsNetTestCases
Change-Id: I562b47f18e345988a2638cf886f86818f9144b91
diff --git a/packages/ConnectivityT/OWNERS b/packages/ConnectivityT/OWNERS
index 4862377..e267d19 100644
--- a/packages/ConnectivityT/OWNERS
+++ b/packages/ConnectivityT/OWNERS
@@ -1 +1,2 @@
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
\ No newline at end of file
+file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp
index 3e82b28..931a55b 100644
--- a/packages/ConnectivityT/framework-t/Android.bp
+++ b/packages/ConnectivityT/framework-t/Android.bp
@@ -100,6 +100,20 @@
     ],
 }
 
+// IpSec related libraries.
+
+filegroup {
+    name: "framework-connectivity-ipsec-sources",
+    srcs: [
+        "src/android/net/IIpSecService.aidl",
+        "src/android/net/IpSec*.*",
+    ],
+    path: "src",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
 // Connectivity-T common libraries.
 
 filegroup {
@@ -116,11 +130,10 @@
 filegroup {
     name: "framework-connectivity-tiramisu-sources",
     srcs: [
+        ":framework-connectivity-ipsec-sources",
         ":framework-connectivity-netstats-sources",
         ":framework-connectivity-nsd-sources",
         ":framework-connectivity-tiramisu-internal-sources",
     ],
-    visibility: [
-        "//frameworks/base",
-    ],
+    visibility: ["//frameworks/base"],
 }
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IIpSecService.aidl b/packages/ConnectivityT/framework-t/src/android/net/IIpSecService.aidl
new file mode 100644
index 0000000..933256a
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/IIpSecService.aidl
@@ -0,0 +1,78 @@
+/*
+** Copyright 2017, 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;
+
+import android.net.LinkAddress;
+import android.net.Network;
+import android.net.IpSecConfig;
+import android.net.IpSecUdpEncapResponse;
+import android.net.IpSecSpiResponse;
+import android.net.IpSecTransformResponse;
+import android.net.IpSecTunnelInterfaceResponse;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * @hide
+ */
+interface IIpSecService
+{
+    IpSecSpiResponse allocateSecurityParameterIndex(
+            in String destinationAddress, int requestedSpi, in IBinder binder);
+
+    void releaseSecurityParameterIndex(int resourceId);
+
+    IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, in IBinder binder);
+
+    void closeUdpEncapsulationSocket(int resourceId);
+
+    IpSecTunnelInterfaceResponse createTunnelInterface(
+            in String localAddr,
+            in String remoteAddr,
+            in Network underlyingNetwork,
+            in IBinder binder,
+            in String callingPackage);
+
+    void addAddressToTunnelInterface(
+            int tunnelResourceId,
+            in LinkAddress localAddr,
+            in String callingPackage);
+
+    void removeAddressFromTunnelInterface(
+            int tunnelResourceId,
+            in LinkAddress localAddr,
+            in String callingPackage);
+
+    void setNetworkForTunnelInterface(
+            int tunnelResourceId, in Network underlyingNetwork, in String callingPackage);
+
+    void deleteTunnelInterface(int resourceId, in String callingPackage);
+
+    IpSecTransformResponse createTransform(
+            in IpSecConfig c, in IBinder binder, in String callingPackage);
+
+    void deleteTransform(int transformId);
+
+    void applyTransportModeTransform(
+            in ParcelFileDescriptor socket, int direction, int transformId);
+
+    void applyTunnelModeTransform(
+            int tunnelResourceId, int direction, int transformResourceId, in String callingPackage);
+
+    void removeTransportModeTransforms(in ParcelFileDescriptor socket);
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java
new file mode 100644
index 0000000..8605248
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.HexDump;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * This class represents a single algorithm that can be used by an {@link IpSecTransform}.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
+ */
+public final class IpSecAlgorithm implements Parcelable {
+    private static final String TAG = "IpSecAlgorithm";
+
+    /**
+     * Null cipher.
+     *
+     * @hide
+     */
+    public static final String CRYPT_NULL = "ecb(cipher_null)";
+
+    /**
+     * AES-CBC Encryption/Ciphering Algorithm.
+     *
+     * <p>Valid lengths for this key are {128, 192, 256}.
+     */
+    public static final String CRYPT_AES_CBC = "cbc(aes)";
+
+    /**
+     * AES-CTR Encryption/Ciphering Algorithm.
+     *
+     * <p>Valid lengths for keying material are {160, 224, 288}.
+     *
+     * <p>As per <a href="https://tools.ietf.org/html/rfc3686#section-5.1">RFC3686 (Section
+     * 5.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit
+     * nonce. RFC compliance requires that the nonce must be unique per security association.
+     *
+     * <p>This algorithm may be available on the device. Caller MUST check if it is supported before
+     * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is
+     * included in the returned algorithm set. The returned algorithm set will not change unless the
+     * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is
+     * requested on an unsupported device.
+     *
+     * <p>@see {@link #getSupportedAlgorithms()}
+     */
+    // This algorithm may be available on devices released before Android 12, and is guaranteed
+    // to be available on devices first shipped with Android 12 or later.
+    public static final String CRYPT_AES_CTR = "rfc3686(ctr(aes))";
+
+    /**
+     * MD5 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
+     * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
+     *
+     * <p>Keys for this algorithm must be 128 bits in length.
+     *
+     * <p>Valid truncation lengths are multiples of 8 bits from 96 to 128.
+     */
+    public static final String AUTH_HMAC_MD5 = "hmac(md5)";
+
+    /**
+     * SHA1 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
+     * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
+     *
+     * <p>Keys for this algorithm must be 160 bits in length.
+     *
+     * <p>Valid truncation lengths are multiples of 8 bits from 96 to 160.
+     */
+    public static final String AUTH_HMAC_SHA1 = "hmac(sha1)";
+
+    /**
+     * SHA256 HMAC Authentication/Integrity Algorithm.
+     *
+     * <p>Keys for this algorithm must be 256 bits in length.
+     *
+     * <p>Valid truncation lengths are multiples of 8 bits from 96 to 256.
+     */
+    public static final String AUTH_HMAC_SHA256 = "hmac(sha256)";
+
+    /**
+     * SHA384 HMAC Authentication/Integrity Algorithm.
+     *
+     * <p>Keys for this algorithm must be 384 bits in length.
+     *
+     * <p>Valid truncation lengths are multiples of 8 bits from 192 to 384.
+     */
+    public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
+
+    /**
+     * SHA512 HMAC Authentication/Integrity Algorithm.
+     *
+     * <p>Keys for this algorithm must be 512 bits in length.
+     *
+     * <p>Valid truncation lengths are multiples of 8 bits from 256 to 512.
+     */
+    public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
+
+    /**
+     * AES-XCBC Authentication/Integrity Algorithm.
+     *
+     * <p>Keys for this algorithm must be 128 bits in length.
+     *
+     * <p>The only valid truncation length is 96 bits.
+     *
+     * <p>This algorithm may be available on the device. Caller MUST check if it is supported before
+     * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is
+     * included in the returned algorithm set. The returned algorithm set will not change unless the
+     * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is
+     * requested on an unsupported device.
+     *
+     * <p>@see {@link #getSupportedAlgorithms()}
+     */
+    // This algorithm may be available on devices released before Android 12, and is guaranteed
+    // to be available on devices first shipped with Android 12 or later.
+    public static final String AUTH_AES_XCBC = "xcbc(aes)";
+
+    /**
+     * AES-CMAC Authentication/Integrity Algorithm.
+     *
+     * <p>Keys for this algorithm must be 128 bits in length.
+     *
+     * <p>The only valid truncation length is 96 bits.
+     *
+     * <p>This algorithm may be available on the device. Caller MUST check if it is supported before
+     * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is
+     * included in the returned algorithm set. The returned algorithm set will not change unless the
+     * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is
+     * requested on an unsupported device.
+     *
+     * <p>@see {@link #getSupportedAlgorithms()}
+     */
+    // This algorithm may be available on devices released before Android 12, and is guaranteed
+    // to be available on devices first shipped with Android 12 or later.
+    public static final String AUTH_AES_CMAC = "cmac(aes)";
+
+    /**
+     * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm.
+     *
+     * <p>Valid lengths for keying material are {160, 224, 288}.
+     *
+     * <p>As per <a href="https://tools.ietf.org/html/rfc4106#section-8.1">RFC4106 (Section
+     * 8.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit
+     * salt. RFC compliance requires that the salt must be unique per invocation with the same key.
+     *
+     * <p>Valid ICV (truncation) lengths are {64, 96, 128}.
+     */
+    public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
+
+    /**
+     * ChaCha20-Poly1305 Authentication/Integrity + Encryption/Ciphering Algorithm.
+     *
+     * <p>Keys for this algorithm must be 288 bits in length.
+     *
+     * <p>As per <a href="https://tools.ietf.org/html/rfc7634#section-2">RFC7634 (Section 2)</a>,
+     * keying material consists of a 256 bit key followed by a 32-bit salt. The salt is fixed per
+     * security association.
+     *
+     * <p>The only valid ICV (truncation) length is 128 bits.
+     *
+     * <p>This algorithm may be available on the device. Caller MUST check if it is supported before
+     * using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is
+     * included in the returned algorithm set. The returned algorithm set will not change unless the
+     * device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is
+     * requested on an unsupported device.
+     *
+     * <p>@see {@link #getSupportedAlgorithms()}
+     */
+    // This algorithm may be available on devices released before Android 12, and is guaranteed
+    // to be available on devices first shipped with Android 12 or later.
+    public static final String AUTH_CRYPT_CHACHA20_POLY1305 = "rfc7539esp(chacha20,poly1305)";
+
+    /** @hide */
+    @StringDef({
+        CRYPT_AES_CBC,
+        CRYPT_AES_CTR,
+        AUTH_HMAC_MD5,
+        AUTH_HMAC_SHA1,
+        AUTH_HMAC_SHA256,
+        AUTH_HMAC_SHA384,
+        AUTH_HMAC_SHA512,
+        AUTH_AES_XCBC,
+        AUTH_AES_CMAC,
+        AUTH_CRYPT_AES_GCM,
+        AUTH_CRYPT_CHACHA20_POLY1305
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AlgorithmName {}
+
+    /** @hide */
+    @VisibleForTesting
+    public static final Map<String, Integer> ALGO_TO_REQUIRED_FIRST_SDK = new HashMap<>();
+
+    private static final int SDK_VERSION_ZERO = 0;
+
+    static {
+        ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CBC, SDK_VERSION_ZERO);
+        ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_MD5, SDK_VERSION_ZERO);
+        ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA1, SDK_VERSION_ZERO);
+        ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA256, SDK_VERSION_ZERO);
+        ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA384, SDK_VERSION_ZERO);
+        ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA512, SDK_VERSION_ZERO);
+        ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_AES_GCM, SDK_VERSION_ZERO);
+
+        ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CTR, Build.VERSION_CODES.S);
+        ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_XCBC, Build.VERSION_CODES.S);
+        ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_CMAC, Build.VERSION_CODES.S);
+        ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_CHACHA20_POLY1305, Build.VERSION_CODES.S);
+    }
+
+    private static final Set<String> ENABLED_ALGOS =
+            Collections.unmodifiableSet(loadAlgos(Resources.getSystem()));
+
+    private final String mName;
+    private final byte[] mKey;
+    private final int mTruncLenBits;
+
+    /**
+     * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
+     * defined as constants in this class.
+     *
+     * <p>For algorithms that produce an integrity check value, the truncation length is a required
+     * parameter. See {@link #IpSecAlgorithm(String algorithm, byte[] key, int truncLenBits)}
+     *
+     * @param algorithm name of the algorithm.
+     * @param key key padded to a multiple of 8 bits.
+     * @throws IllegalArgumentException if algorithm or key length is invalid.
+     */
+    public IpSecAlgorithm(@NonNull @AlgorithmName String algorithm, @NonNull byte[] key) {
+        this(algorithm, key, 0);
+    }
+
+    /**
+     * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
+     * defined as constants in this class.
+     *
+     * <p>This constructor only supports algorithms that use a truncation length. i.e.
+     * Authentication and Authenticated Encryption algorithms.
+     *
+     * @param algorithm name of the algorithm.
+     * @param key key padded to a multiple of 8 bits.
+     * @param truncLenBits number of bits of output hash to use.
+     * @throws IllegalArgumentException if algorithm, key length or truncation length is invalid.
+     */
+    public IpSecAlgorithm(
+            @NonNull @AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) {
+        mName = algorithm;
+        mKey = key.clone();
+        mTruncLenBits = truncLenBits;
+        checkValidOrThrow(mName, mKey.length * 8, mTruncLenBits);
+    }
+
+    /** Get the algorithm name */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    /** Get the key for this algorithm */
+    @NonNull
+    public byte[] getKey() {
+        return mKey.clone();
+    }
+
+    /** Get the truncation length of this algorithm, in bits */
+    public int getTruncationLengthBits() {
+        return mTruncLenBits;
+    }
+
+    /* Parcelable Implementation */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Write to parcel */
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mName);
+        out.writeByteArray(mKey);
+        out.writeInt(mTruncLenBits);
+    }
+
+    /** Parcelable Creator */
+    public static final @android.annotation.NonNull Parcelable.Creator<IpSecAlgorithm> CREATOR =
+            new Parcelable.Creator<IpSecAlgorithm>() {
+                public IpSecAlgorithm createFromParcel(Parcel in) {
+                    final String name = in.readString();
+                    final byte[] key = in.createByteArray();
+                    final int truncLenBits = in.readInt();
+
+                    return new IpSecAlgorithm(name, key, truncLenBits);
+                }
+
+                public IpSecAlgorithm[] newArray(int size) {
+                    return new IpSecAlgorithm[size];
+                }
+            };
+
+    /**
+     * Returns supported IPsec algorithms for the current device.
+     *
+     * <p>Some algorithms may not be supported on old devices. Callers MUST check if an algorithm is
+     * supported before using it.
+     */
+    @NonNull
+    public static Set<String> getSupportedAlgorithms() {
+        return ENABLED_ALGOS;
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public static Set<String> loadAlgos(Resources systemResources) {
+        final Set<String> enabledAlgos = new HashSet<>();
+
+        // Load and validate the optional algorithm resource. Undefined or duplicate algorithms in
+        // the resource are not allowed.
+        final String[] resourceAlgos = systemResources.getStringArray(
+                com.android.internal.R.array.config_optionalIpSecAlgorithms);
+        for (String str : resourceAlgos) {
+            if (!ALGO_TO_REQUIRED_FIRST_SDK.containsKey(str) || !enabledAlgos.add(str)) {
+                // This error should be caught by CTS and never be thrown to API callers
+                throw new IllegalArgumentException("Invalid or repeated algorithm " + str);
+            }
+        }
+
+        for (Entry<String, Integer> entry : ALGO_TO_REQUIRED_FIRST_SDK.entrySet()) {
+            if (Build.VERSION.DEVICE_INITIAL_SDK_INT >= entry.getValue()) {
+                enabledAlgos.add(entry.getKey());
+            }
+        }
+
+        return enabledAlgos;
+    }
+
+    private static void checkValidOrThrow(String name, int keyLen, int truncLen) {
+        final boolean isValidLen;
+        final boolean isValidTruncLen;
+
+        if (!getSupportedAlgorithms().contains(name)) {
+            throw new IllegalArgumentException("Unsupported algorithm: " + name);
+        }
+
+        switch (name) {
+            case CRYPT_AES_CBC:
+                isValidLen = keyLen == 128 || keyLen == 192 || keyLen == 256;
+                isValidTruncLen = true;
+                break;
+            case CRYPT_AES_CTR:
+                // The keying material for AES-CTR is a key plus a 32-bit salt
+                isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32;
+                isValidTruncLen = true;
+                break;
+            case AUTH_HMAC_MD5:
+                isValidLen = keyLen == 128;
+                isValidTruncLen = truncLen >= 96 && truncLen <= 128;
+                break;
+            case AUTH_HMAC_SHA1:
+                isValidLen = keyLen == 160;
+                isValidTruncLen = truncLen >= 96 && truncLen <= 160;
+                break;
+            case AUTH_HMAC_SHA256:
+                isValidLen = keyLen == 256;
+                isValidTruncLen = truncLen >= 96 && truncLen <= 256;
+                break;
+            case AUTH_HMAC_SHA384:
+                isValidLen = keyLen == 384;
+                isValidTruncLen = truncLen >= 192 && truncLen <= 384;
+                break;
+            case AUTH_HMAC_SHA512:
+                isValidLen = keyLen == 512;
+                isValidTruncLen = truncLen >= 256 && truncLen <= 512;
+                break;
+            case AUTH_AES_XCBC:
+                isValidLen = keyLen == 128;
+                isValidTruncLen = truncLen == 96;
+                break;
+            case AUTH_AES_CMAC:
+                isValidLen = keyLen == 128;
+                isValidTruncLen = truncLen == 96;
+                break;
+            case AUTH_CRYPT_AES_GCM:
+                // The keying material for GCM is a key plus a 32-bit salt
+                isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32;
+                isValidTruncLen = truncLen == 64 || truncLen == 96 || truncLen == 128;
+                break;
+            case AUTH_CRYPT_CHACHA20_POLY1305:
+                // The keying material for ChaCha20Poly1305 is a key plus a 32-bit salt
+                isValidLen = keyLen == 256 + 32;
+                isValidTruncLen = truncLen == 128;
+                break;
+            default:
+                // Should never hit here.
+                throw new IllegalArgumentException("Couldn't find an algorithm: " + name);
+        }
+
+        if (!isValidLen) {
+            throw new IllegalArgumentException("Invalid key material keyLength: " + keyLen);
+        }
+        if (!isValidTruncLen) {
+            throw new IllegalArgumentException("Invalid truncation keyLength: " + truncLen);
+        }
+    }
+
+    /** @hide */
+    public boolean isAuthentication() {
+        switch (getName()) {
+            // Fallthrough
+            case AUTH_HMAC_MD5:
+            case AUTH_HMAC_SHA1:
+            case AUTH_HMAC_SHA256:
+            case AUTH_HMAC_SHA384:
+            case AUTH_HMAC_SHA512:
+            case AUTH_AES_XCBC:
+            case AUTH_AES_CMAC:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /** @hide */
+    public boolean isEncryption() {
+        switch (getName()) {
+            case CRYPT_AES_CBC: // fallthrough
+            case CRYPT_AES_CTR:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /** @hide */
+    public boolean isAead() {
+        switch (getName()) {
+            case AUTH_CRYPT_AES_GCM: // fallthrough
+            case AUTH_CRYPT_CHACHA20_POLY1305:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    // Because encryption keys are sensitive and userdebug builds are used by large user pools
+    // such as beta testers, we only allow sensitive info such as keys on eng builds.
+    private static boolean isUnsafeBuild() {
+        return Build.IS_DEBUGGABLE && Build.IS_ENG;
+    }
+
+    @Override
+    @NonNull
+    public String toString() {
+        return new StringBuilder()
+                .append("{mName=")
+                .append(mName)
+                .append(", mKey=")
+                .append(isUnsafeBuild() ? HexDump.toHexString(mKey) : "<hidden>")
+                .append(", mTruncLenBits=")
+                .append(mTruncLenBits)
+                .append("}")
+                .toString();
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public static boolean equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs) {
+        if (lhs == null || rhs == null) return (lhs == rhs);
+        return (lhs.mName.equals(rhs.mName)
+                && Arrays.equals(lhs.mKey, rhs.mKey)
+                && lhs.mTruncLenBits == rhs.mTruncLenBits);
+    }
+};
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.aidl
new file mode 100644
index 0000000..eaefca7
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 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;
+
+/** @hide */
+parcelable IpSecConfig;
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java
new file mode 100644
index 0000000..575c5ed
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * This class encapsulates all the configuration parameters needed to create IPsec transforms and
+ * policies.
+ *
+ * @hide
+ */
+public final class IpSecConfig implements Parcelable {
+    private static final String TAG = "IpSecConfig";
+
+    // MODE_TRANSPORT or MODE_TUNNEL
+    private int mMode = IpSecTransform.MODE_TRANSPORT;
+
+    // Preventing this from being null simplifies Java->Native binder
+    private String mSourceAddress = "";
+
+    // Preventing this from being null simplifies Java->Native binder
+    private String mDestinationAddress = "";
+
+    // The underlying Network that represents the "gateway" Network
+    // for outbound packets. It may also be used to select packets.
+    private Network mNetwork;
+
+    // Minimum requirements for identifying a transform
+    // SPI identifying the IPsec SA in packet processing
+    // and a destination IP address
+    private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID;
+
+    // Encryption Algorithm
+    private IpSecAlgorithm mEncryption;
+
+    // Authentication Algorithm
+    private IpSecAlgorithm mAuthentication;
+
+    // Authenticated Encryption Algorithm
+    private IpSecAlgorithm mAuthenticatedEncryption;
+
+    // For tunnel mode IPv4 UDP Encapsulation
+    // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE
+    private int mEncapType = IpSecTransform.ENCAP_NONE;
+    private int mEncapSocketResourceId = IpSecManager.INVALID_RESOURCE_ID;
+    private int mEncapRemotePort;
+
+    // An interval, in seconds between the NattKeepalive packets
+    private int mNattKeepaliveInterval;
+
+    // XFRM mark and mask; defaults to 0 (no mark/mask)
+    private int mMarkValue;
+    private int mMarkMask;
+
+    // XFRM interface id
+    private int mXfrmInterfaceId;
+
+    /** Set the mode for this IPsec transform */
+    public void setMode(int mode) {
+        mMode = mode;
+    }
+
+    /** Set the source IP addres for this IPsec transform */
+    public void setSourceAddress(String sourceAddress) {
+        mSourceAddress = sourceAddress;
+    }
+
+    /** Set the destination IP address for this IPsec transform */
+    public void setDestinationAddress(String destinationAddress) {
+        mDestinationAddress = destinationAddress;
+    }
+
+    /** Set the SPI by resource ID */
+    public void setSpiResourceId(int resourceId) {
+        mSpiResourceId = resourceId;
+    }
+
+    /** Set the encryption algorithm */
+    public void setEncryption(IpSecAlgorithm encryption) {
+        mEncryption = encryption;
+    }
+
+    /** Set the authentication algorithm */
+    public void setAuthentication(IpSecAlgorithm authentication) {
+        mAuthentication = authentication;
+    }
+
+    /** Set the authenticated encryption algorithm */
+    public void setAuthenticatedEncryption(IpSecAlgorithm authenticatedEncryption) {
+        mAuthenticatedEncryption = authenticatedEncryption;
+    }
+
+    /** Set the underlying network that will carry traffic for this transform */
+    public void setNetwork(Network network) {
+        mNetwork = network;
+    }
+
+    public void setEncapType(int encapType) {
+        mEncapType = encapType;
+    }
+
+    public void setEncapSocketResourceId(int resourceId) {
+        mEncapSocketResourceId = resourceId;
+    }
+
+    public void setEncapRemotePort(int port) {
+        mEncapRemotePort = port;
+    }
+
+    public void setNattKeepaliveInterval(int interval) {
+        mNattKeepaliveInterval = interval;
+    }
+
+    /**
+     * Sets the mark value
+     *
+     * <p>Internal (System server) use only. Marks passed in by users will be overwritten or
+     * ignored.
+     */
+    public void setMarkValue(int mark) {
+        mMarkValue = mark;
+    }
+
+    /**
+     * Sets the mark mask
+     *
+     * <p>Internal (System server) use only. Marks passed in by users will be overwritten or
+     * ignored.
+     */
+    public void setMarkMask(int mask) {
+        mMarkMask = mask;
+    }
+
+    public void setXfrmInterfaceId(int xfrmInterfaceId) {
+        mXfrmInterfaceId = xfrmInterfaceId;
+    }
+
+    // Transport or Tunnel
+    public int getMode() {
+        return mMode;
+    }
+
+    public String getSourceAddress() {
+        return mSourceAddress;
+    }
+
+    public int getSpiResourceId() {
+        return mSpiResourceId;
+    }
+
+    public String getDestinationAddress() {
+        return mDestinationAddress;
+    }
+
+    public IpSecAlgorithm getEncryption() {
+        return mEncryption;
+    }
+
+    public IpSecAlgorithm getAuthentication() {
+        return mAuthentication;
+    }
+
+    public IpSecAlgorithm getAuthenticatedEncryption() {
+        return mAuthenticatedEncryption;
+    }
+
+    public Network getNetwork() {
+        return mNetwork;
+    }
+
+    public int getEncapType() {
+        return mEncapType;
+    }
+
+    public int getEncapSocketResourceId() {
+        return mEncapSocketResourceId;
+    }
+
+    public int getEncapRemotePort() {
+        return mEncapRemotePort;
+    }
+
+    public int getNattKeepaliveInterval() {
+        return mNattKeepaliveInterval;
+    }
+
+    public int getMarkValue() {
+        return mMarkValue;
+    }
+
+    public int getMarkMask() {
+        return mMarkMask;
+    }
+
+    public int getXfrmInterfaceId() {
+        return mXfrmInterfaceId;
+    }
+
+    // Parcelable Methods
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mMode);
+        out.writeString(mSourceAddress);
+        out.writeString(mDestinationAddress);
+        out.writeParcelable(mNetwork, flags);
+        out.writeInt(mSpiResourceId);
+        out.writeParcelable(mEncryption, flags);
+        out.writeParcelable(mAuthentication, flags);
+        out.writeParcelable(mAuthenticatedEncryption, flags);
+        out.writeInt(mEncapType);
+        out.writeInt(mEncapSocketResourceId);
+        out.writeInt(mEncapRemotePort);
+        out.writeInt(mNattKeepaliveInterval);
+        out.writeInt(mMarkValue);
+        out.writeInt(mMarkMask);
+        out.writeInt(mXfrmInterfaceId);
+    }
+
+    @VisibleForTesting
+    public IpSecConfig() {}
+
+    /** Copy constructor */
+    @VisibleForTesting
+    public IpSecConfig(IpSecConfig c) {
+        mMode = c.mMode;
+        mSourceAddress = c.mSourceAddress;
+        mDestinationAddress = c.mDestinationAddress;
+        mNetwork = c.mNetwork;
+        mSpiResourceId = c.mSpiResourceId;
+        mEncryption = c.mEncryption;
+        mAuthentication = c.mAuthentication;
+        mAuthenticatedEncryption = c.mAuthenticatedEncryption;
+        mEncapType = c.mEncapType;
+        mEncapSocketResourceId = c.mEncapSocketResourceId;
+        mEncapRemotePort = c.mEncapRemotePort;
+        mNattKeepaliveInterval = c.mNattKeepaliveInterval;
+        mMarkValue = c.mMarkValue;
+        mMarkMask = c.mMarkMask;
+        mXfrmInterfaceId = c.mXfrmInterfaceId;
+    }
+
+    private IpSecConfig(Parcel in) {
+        mMode = in.readInt();
+        mSourceAddress = in.readString();
+        mDestinationAddress = in.readString();
+        mNetwork = (Network) in.readParcelable(Network.class.getClassLoader());
+        mSpiResourceId = in.readInt();
+        mEncryption =
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+        mAuthentication =
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+        mAuthenticatedEncryption =
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+        mEncapType = in.readInt();
+        mEncapSocketResourceId = in.readInt();
+        mEncapRemotePort = in.readInt();
+        mNattKeepaliveInterval = in.readInt();
+        mMarkValue = in.readInt();
+        mMarkMask = in.readInt();
+        mXfrmInterfaceId = in.readInt();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder strBuilder = new StringBuilder();
+        strBuilder
+                .append("{mMode=")
+                .append(mMode == IpSecTransform.MODE_TUNNEL ? "TUNNEL" : "TRANSPORT")
+                .append(", mSourceAddress=")
+                .append(mSourceAddress)
+                .append(", mDestinationAddress=")
+                .append(mDestinationAddress)
+                .append(", mNetwork=")
+                .append(mNetwork)
+                .append(", mEncapType=")
+                .append(mEncapType)
+                .append(", mEncapSocketResourceId=")
+                .append(mEncapSocketResourceId)
+                .append(", mEncapRemotePort=")
+                .append(mEncapRemotePort)
+                .append(", mNattKeepaliveInterval=")
+                .append(mNattKeepaliveInterval)
+                .append("{mSpiResourceId=")
+                .append(mSpiResourceId)
+                .append(", mEncryption=")
+                .append(mEncryption)
+                .append(", mAuthentication=")
+                .append(mAuthentication)
+                .append(", mAuthenticatedEncryption=")
+                .append(mAuthenticatedEncryption)
+                .append(", mMarkValue=")
+                .append(mMarkValue)
+                .append(", mMarkMask=")
+                .append(mMarkMask)
+                .append(", mXfrmInterfaceId=")
+                .append(mXfrmInterfaceId)
+                .append("}");
+
+        return strBuilder.toString();
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<IpSecConfig> CREATOR =
+            new Parcelable.Creator<IpSecConfig>() {
+                public IpSecConfig createFromParcel(Parcel in) {
+                    return new IpSecConfig(in);
+                }
+
+                public IpSecConfig[] newArray(int size) {
+                    return new IpSecConfig[size];
+                }
+            };
+
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (!(other instanceof IpSecConfig)) return false;
+        final IpSecConfig rhs = (IpSecConfig) other;
+        return (mMode == rhs.mMode
+                && mSourceAddress.equals(rhs.mSourceAddress)
+                && mDestinationAddress.equals(rhs.mDestinationAddress)
+                && ((mNetwork != null && mNetwork.equals(rhs.mNetwork))
+                        || (mNetwork == rhs.mNetwork))
+                && mEncapType == rhs.mEncapType
+                && mEncapSocketResourceId == rhs.mEncapSocketResourceId
+                && mEncapRemotePort == rhs.mEncapRemotePort
+                && mNattKeepaliveInterval == rhs.mNattKeepaliveInterval
+                && mSpiResourceId == rhs.mSpiResourceId
+                && IpSecAlgorithm.equals(mEncryption, rhs.mEncryption)
+                && IpSecAlgorithm.equals(mAuthenticatedEncryption, rhs.mAuthenticatedEncryption)
+                && IpSecAlgorithm.equals(mAuthentication, rhs.mAuthentication)
+                && mMarkValue == rhs.mMarkValue
+                && mMarkMask == rhs.mMarkMask
+                && mXfrmInterfaceId == rhs.mXfrmInterfaceId);
+    }
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
new file mode 100644
index 0000000..c106807
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
@@ -0,0 +1,1034 @@
+/*
+ * Copyright (C) 2017 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;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+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.net.annotations.PolicyDirection;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.util.AndroidException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import dalvik.system.CloseGuard;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.Socket;
+
+/**
+ * This class contains methods for managing IPsec sessions. Once configured, the kernel will apply
+ * confidentiality (encryption) and integrity (authentication) to IP traffic.
+ *
+ * <p>Note that not all aspects of IPsec are permitted by this API. Applications may create
+ * transport mode security associations and apply them to individual sockets. Applications looking
+ * to create an IPsec VPN should use {@link VpnManager} and {@link Ikev2VpnProfile}.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ *     Internet Protocol</a>
+ */
+@SystemService(Context.IPSEC_SERVICE)
+public final class IpSecManager {
+    private static final String TAG = "IpSecManager";
+
+    /**
+     * Used when applying a transform to direct traffic through an {@link IpSecTransform}
+     * towards the host.
+     *
+     * <p>See {@link #applyTransportModeTransform(Socket, int, IpSecTransform)}.
+     */
+    public static final int DIRECTION_IN = 0;
+
+    /**
+     * Used when applying a transform to direct traffic through an {@link IpSecTransform}
+     * away from the host.
+     *
+     * <p>See {@link #applyTransportModeTransform(Socket, int, IpSecTransform)}.
+     */
+    public static final int DIRECTION_OUT = 1;
+
+    /**
+     * Used when applying a transform to direct traffic through an {@link IpSecTransform} for
+     * forwarding between interfaces.
+     *
+     * <p>See {@link #applyTransportModeTransform(Socket, int, IpSecTransform)}.
+     *
+     * @hide
+     */
+    public static final int DIRECTION_FWD = 2;
+
+    /**
+     * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index.
+     *
+     * <p>No IPsec packet may contain an SPI of 0.
+     *
+     * @hide
+     */
+    @TestApi public static final int INVALID_SECURITY_PARAMETER_INDEX = 0;
+
+    /** @hide */
+    public interface Status {
+        public static final int OK = 0;
+        public static final int RESOURCE_UNAVAILABLE = 1;
+        public static final int SPI_UNAVAILABLE = 2;
+    }
+
+    /** @hide */
+    public static final int INVALID_RESOURCE_ID = -1;
+
+    /**
+     * Thrown to indicate that a requested SPI is in use.
+     *
+     * <p>The combination of remote {@code InetAddress} and SPI must be unique across all apps on
+     * one device. If this error is encountered, a new SPI is required before a transform may be
+     * created. This error can be avoided by calling {@link
+     * IpSecManager#allocateSecurityParameterIndex}.
+     */
+    public static final class SpiUnavailableException extends AndroidException {
+        private final int mSpi;
+
+        /**
+         * Construct an exception indicating that a transform with the given SPI is already in use
+         * or otherwise unavailable.
+         *
+         * @param msg description indicating the colliding SPI
+         * @param spi the SPI that could not be used due to a collision
+         */
+        SpiUnavailableException(String msg, int spi) {
+            super(msg + " (spi: " + spi + ")");
+            mSpi = spi;
+        }
+
+        /** Get the SPI that caused a collision. */
+        public int getSpi() {
+            return mSpi;
+        }
+    }
+
+    /**
+     * Thrown to indicate that an IPsec resource is unavailable.
+     *
+     * <p>This could apply to resources such as sockets, {@link SecurityParameterIndex}, {@link
+     * IpSecTransform}, or other system resources. If this exception is thrown, users should release
+     * allocated objects of the type requested.
+     */
+    public static final class ResourceUnavailableException extends AndroidException {
+
+        ResourceUnavailableException(String msg) {
+            super(msg);
+        }
+    }
+
+    private final Context mContext;
+    private final IIpSecService mService;
+
+    /**
+     * This class represents a reserved SPI.
+     *
+     * <p>Objects of this type are used to track reserved security parameter indices. They can be
+     * obtained by calling {@link IpSecManager#allocateSecurityParameterIndex} and must be released
+     * by calling {@link #close()} when they are no longer needed.
+     */
+    public static final class SecurityParameterIndex implements AutoCloseable {
+        private final IIpSecService mService;
+        private final InetAddress mDestinationAddress;
+        private final CloseGuard mCloseGuard = CloseGuard.get();
+        private int mSpi = INVALID_SECURITY_PARAMETER_INDEX;
+        private int mResourceId = INVALID_RESOURCE_ID;
+
+        /** Get the underlying SPI held by this object. */
+        public int getSpi() {
+            return mSpi;
+        }
+
+        /**
+         * Release an SPI that was previously reserved.
+         *
+         * <p>Release an SPI for use by other users in the system. If a SecurityParameterIndex is
+         * applied to an IpSecTransform, it will become unusable for future transforms but should
+         * still be closed to ensure system resources are released.
+         */
+        @Override
+        public void close() {
+            try {
+                mService.releaseSecurityParameterIndex(mResourceId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            } catch (Exception e) {
+                // On close we swallow all random exceptions since failure to close is not
+                // actionable by the user.
+                Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
+            } finally {
+                mResourceId = INVALID_RESOURCE_ID;
+                mCloseGuard.close();
+            }
+        }
+
+        /** Check that the SPI was closed properly. */
+        @Override
+        protected void finalize() throws Throwable {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+
+            close();
+        }
+
+        private SecurityParameterIndex(
+                @NonNull IIpSecService service, InetAddress destinationAddress, int spi)
+                throws ResourceUnavailableException, SpiUnavailableException {
+            mService = service;
+            mDestinationAddress = destinationAddress;
+            try {
+                IpSecSpiResponse result =
+                        mService.allocateSecurityParameterIndex(
+                                destinationAddress.getHostAddress(), spi, new Binder());
+
+                if (result == null) {
+                    throw new NullPointerException("Received null response from IpSecService");
+                }
+
+                int status = result.status;
+                switch (status) {
+                    case Status.OK:
+                        break;
+                    case Status.RESOURCE_UNAVAILABLE:
+                        throw new ResourceUnavailableException(
+                                "No more SPIs may be allocated by this requester.");
+                    case Status.SPI_UNAVAILABLE:
+                        throw new SpiUnavailableException("Requested SPI is unavailable", spi);
+                    default:
+                        throw new RuntimeException(
+                                "Unknown status returned by IpSecService: " + status);
+                }
+                mSpi = result.spi;
+                mResourceId = result.resourceId;
+
+                if (mSpi == INVALID_SECURITY_PARAMETER_INDEX) {
+                    throw new RuntimeException("Invalid SPI returned by IpSecService: " + status);
+                }
+
+                if (mResourceId == INVALID_RESOURCE_ID) {
+                    throw new RuntimeException(
+                            "Invalid Resource ID returned by IpSecService: " + status);
+                }
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            mCloseGuard.open("open");
+        }
+
+        /** @hide */
+        @VisibleForTesting
+        public int getResourceId() {
+            return mResourceId;
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder()
+                .append("SecurityParameterIndex{spi=")
+                .append(mSpi)
+                .append(",resourceId=")
+                .append(mResourceId)
+                .append("}")
+                .toString();
+        }
+    }
+
+    /**
+     * Reserve a random SPI for traffic bound to or from the specified destination address.
+     *
+     * <p>If successful, this SPI is guaranteed available until released by a call to {@link
+     * SecurityParameterIndex#close()}.
+     *
+     * @param destinationAddress the destination address for traffic bearing the requested SPI.
+     *     For inbound traffic, the destination should be an address currently assigned on-device.
+     * @return the reserved SecurityParameterIndex
+     * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are
+     *     currently allocated for this user
+     */
+    @NonNull
+    public SecurityParameterIndex allocateSecurityParameterIndex(
+                @NonNull InetAddress destinationAddress) throws ResourceUnavailableException {
+        try {
+            return new SecurityParameterIndex(
+                    mService,
+                    destinationAddress,
+                    IpSecManager.INVALID_SECURITY_PARAMETER_INDEX);
+        } catch (ServiceSpecificException e) {
+            throw rethrowUncheckedExceptionFromServiceSpecificException(e);
+        } catch (SpiUnavailableException unlikely) {
+            // Because this function allocates a totally random SPI, it really shouldn't ever
+            // fail to allocate an SPI; we simply need this because the exception is checked.
+            throw new ResourceUnavailableException("No SPIs available");
+        }
+    }
+
+    /**
+     * Reserve the requested SPI for traffic bound to or from the specified destination address.
+     *
+     * <p>If successful, this SPI is guaranteed available until released by a call to {@link
+     * SecurityParameterIndex#close()}.
+     *
+     * @param destinationAddress the destination address for traffic bearing the requested SPI.
+     *     For inbound traffic, the destination should be an address currently assigned on-device.
+     * @param requestedSpi the requested SPI. The range 1-255 is reserved and may not be used. See
+     *     RFC 4303 Section 2.1.
+     * @return the reserved SecurityParameterIndex
+     * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are
+     *     currently allocated for this user
+     * @throws {@link #SpiUnavailableException} indicating that the requested SPI could not be
+     *     reserved
+     */
+    @NonNull
+    public SecurityParameterIndex allocateSecurityParameterIndex(
+            @NonNull InetAddress destinationAddress, int requestedSpi)
+            throws SpiUnavailableException, ResourceUnavailableException {
+        if (requestedSpi == IpSecManager.INVALID_SECURITY_PARAMETER_INDEX) {
+            throw new IllegalArgumentException("Requested SPI must be a valid (non-zero) SPI");
+        }
+        try {
+            return new SecurityParameterIndex(mService, destinationAddress, requestedSpi);
+        } catch (ServiceSpecificException e) {
+            throw rethrowUncheckedExceptionFromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Apply an IPsec transform to a stream socket.
+     *
+     * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+     * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+     * the transform is removed from the socket by calling {@link #removeTransportModeTransforms},
+     * unprotected traffic can resume on that socket.
+     *
+     * <p>For security reasons, the destination address of any traffic on the socket must match the
+     * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+     * other IP address will result in an IOException. In addition, reads and writes on the socket
+     * will throw IOException if the user deactivates the transform (by calling {@link
+     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}.
+     *
+     * <p>Note that when applied to TCP sockets, calling {@link IpSecTransform#close()} on an
+     * applied transform before completion of graceful shutdown may result in the shutdown sequence
+     * failing to complete. As such, applications requiring graceful shutdown MUST close the socket
+     * prior to deactivating the applied transform. Socket closure may be performed asynchronously
+     * (in batches), so the returning of a close function does not guarantee shutdown of a socket.
+     * Setting an SO_LINGER timeout results in socket closure being performed synchronously, and is
+     * sufficient to ensure shutdown.
+     *
+     * Specifically, if the transform is deactivated (by calling {@link IpSecTransform#close()}),
+     * prior to the socket being closed, the standard [FIN - FIN/ACK - ACK], or the reset [RST]
+     * packets are dropped due to the lack of a valid Transform. Similarly, if a socket without the
+     * SO_LINGER option set is closed, the delayed/batched FIN packets may be dropped.
+     *
+     * <h4>Rekey Procedure</h4>
+     *
+     * <p>When applying a new tranform to a socket in the outbound direction, the previous transform
+     * will be removed and the new transform will take effect immediately, sending all traffic on
+     * the new transform; however, when applying a transform in the inbound direction, traffic
+     * on the old transform will continue to be decrypted and delivered until that transform is
+     * deallocated by calling {@link IpSecTransform#close()}. This overlap allows lossless rekey
+     * procedures where both transforms are valid until both endpoints are using the new transform
+     * and all in-flight packets have been received.
+     *
+     * @param socket a stream socket
+     * @param direction the direction in which the transform should be applied
+     * @param transform a transport mode {@code IpSecTransform}
+     * @throws IOException indicating that the transform could not be applied
+     */
+    public void applyTransportModeTransform(@NonNull Socket socket,
+            @PolicyDirection int direction, @NonNull IpSecTransform transform) throws IOException {
+        // Ensure creation of FD. See b/77548890 for more details.
+        socket.getSoLinger();
+
+        applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform);
+    }
+
+    /**
+     * Apply an IPsec transform to a datagram socket.
+     *
+     * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+     * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+     * the transform is removed from the socket by calling {@link #removeTransportModeTransforms},
+     * unprotected traffic can resume on that socket.
+     *
+     * <p>For security reasons, the destination address of any traffic on the socket must match the
+     * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+     * other IP address will result in an IOException. In addition, reads and writes on the socket
+     * will throw IOException if the user deactivates the transform (by calling {@link
+     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}.
+     *
+     * <h4>Rekey Procedure</h4>
+     *
+     * <p>When applying a new tranform to a socket in the outbound direction, the previous transform
+     * will be removed and the new transform will take effect immediately, sending all traffic on
+     * the new transform; however, when applying a transform in the inbound direction, traffic
+     * on the old transform will continue to be decrypted and delivered until that transform is
+     * deallocated by calling {@link IpSecTransform#close()}. This overlap allows lossless rekey
+     * procedures where both transforms are valid until both endpoints are using the new transform
+     * and all in-flight packets have been received.
+     *
+     * @param socket a datagram socket
+     * @param direction the direction in which the transform should be applied
+     * @param transform a transport mode {@code IpSecTransform}
+     * @throws IOException indicating that the transform could not be applied
+     */
+    public void applyTransportModeTransform(@NonNull DatagramSocket socket,
+            @PolicyDirection int direction, @NonNull IpSecTransform transform) throws IOException {
+        applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform);
+    }
+
+    /**
+     * Apply an IPsec transform to a socket.
+     *
+     * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+     * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+     * the transform is removed from the socket by calling {@link #removeTransportModeTransforms},
+     * unprotected traffic can resume on that socket.
+     *
+     * <p>For security reasons, the destination address of any traffic on the socket must match the
+     * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+     * other IP address will result in an IOException. In addition, reads and writes on the socket
+     * will throw IOException if the user deactivates the transform (by calling {@link
+     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}.
+     *
+     * <p>Note that when applied to TCP sockets, calling {@link IpSecTransform#close()} on an
+     * applied transform before completion of graceful shutdown may result in the shutdown sequence
+     * failing to complete. As such, applications requiring graceful shutdown MUST close the socket
+     * prior to deactivating the applied transform. Socket closure may be performed asynchronously
+     * (in batches), so the returning of a close function does not guarantee shutdown of a socket.
+     * Setting an SO_LINGER timeout results in socket closure being performed synchronously, and is
+     * sufficient to ensure shutdown.
+     *
+     * Specifically, if the transform is deactivated (by calling {@link IpSecTransform#close()}),
+     * prior to the socket being closed, the standard [FIN - FIN/ACK - ACK], or the reset [RST]
+     * packets are dropped due to the lack of a valid Transform. Similarly, if a socket without the
+     * SO_LINGER option set is closed, the delayed/batched FIN packets may be dropped.
+     *
+     * <h4>Rekey Procedure</h4>
+     *
+     * <p>When applying a new tranform to a socket in the outbound direction, the previous transform
+     * will be removed and the new transform will take effect immediately, sending all traffic on
+     * the new transform; however, when applying a transform in the inbound direction, traffic
+     * on the old transform will continue to be decrypted and delivered until that transform is
+     * deallocated by calling {@link IpSecTransform#close()}. This overlap allows lossless rekey
+     * procedures where both transforms are valid until both endpoints are using the new transform
+     * and all in-flight packets have been received.
+     *
+     * @param socket a socket file descriptor
+     * @param direction the direction in which the transform should be applied
+     * @param transform a transport mode {@code IpSecTransform}
+     * @throws IOException indicating that the transform could not be applied
+     */
+    public void applyTransportModeTransform(@NonNull FileDescriptor socket,
+            @PolicyDirection int direction, @NonNull IpSecTransform transform) throws IOException {
+        // We dup() the FileDescriptor here because if we don't, then the ParcelFileDescriptor()
+        // constructor takes control and closes the user's FD when we exit the method.
+        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) {
+            mService.applyTransportModeTransform(pfd, direction, transform.getResourceId());
+        } catch (ServiceSpecificException e) {
+            throw rethrowCheckedExceptionFromServiceSpecificException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Remove an IPsec transform from a stream socket.
+     *
+     * <p>Once removed, traffic on the socket will not be encrypted. Removing transforms from a
+     * socket allows the socket to be reused for communication in the clear.
+     *
+     * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+     * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+     * is called.
+     *
+     * @param socket a socket that previously had a transform applied to it
+     * @throws IOException indicating that the transform could not be removed from the socket
+     */
+    public void removeTransportModeTransforms(@NonNull Socket socket) throws IOException {
+        // Ensure creation of FD. See b/77548890 for more details.
+        socket.getSoLinger();
+
+        removeTransportModeTransforms(socket.getFileDescriptor$());
+    }
+
+    /**
+     * Remove an IPsec transform from a datagram socket.
+     *
+     * <p>Once removed, traffic on the socket will not be encrypted. Removing transforms from a
+     * socket allows the socket to be reused for communication in the clear.
+     *
+     * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+     * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+     * is called.
+     *
+     * @param socket a socket that previously had a transform applied to it
+     * @throws IOException indicating that the transform could not be removed from the socket
+     */
+    public void removeTransportModeTransforms(@NonNull DatagramSocket socket) throws IOException {
+        removeTransportModeTransforms(socket.getFileDescriptor$());
+    }
+
+    /**
+     * Remove an IPsec transform from a socket.
+     *
+     * <p>Once removed, traffic on the socket will not be encrypted. Removing transforms from a
+     * socket allows the socket to be reused for communication in the clear.
+     *
+     * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+     * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+     * is called.
+     *
+     * @param socket a socket that previously had a transform applied to it
+     * @throws IOException indicating that the transform could not be removed from the socket
+     */
+    public void removeTransportModeTransforms(@NonNull FileDescriptor socket) throws IOException {
+        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) {
+            mService.removeTransportModeTransforms(pfd);
+        } catch (ServiceSpecificException e) {
+            throw rethrowCheckedExceptionFromServiceSpecificException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Remove a Tunnel Mode IPsec Transform from a {@link Network}. This must be used as part of
+     * cleanup if a tunneled Network experiences a change in default route. The Network will drop
+     * all traffic that cannot be routed to the Tunnel's outbound interface. If that interface is
+     * lost, all traffic will drop.
+     *
+     * <p>TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked.
+     *
+     * @param net a network that currently has transform applied to it.
+     * @param transform a Tunnel Mode IPsec Transform that has been previously applied to the given
+     *     network
+     * @hide
+     */
+    public void removeTunnelModeTransform(Network net, IpSecTransform transform) {}
+
+    /**
+     * This class provides access to a UDP encapsulation Socket.
+     *
+     * <p>{@code UdpEncapsulationSocket} wraps a system-provided datagram socket intended for IKEv2
+     * signalling and UDP encapsulated IPsec traffic. Instances can be obtained by calling {@link
+     * IpSecManager#openUdpEncapsulationSocket}. The provided socket cannot be re-bound by the
+     * caller. The caller should not close the {@code FileDescriptor} returned by {@link
+     * #getFileDescriptor}, but should use {@link #close} instead.
+     *
+     * <p>Allowing the user to close or unbind a UDP encapsulation socket could impact the traffic
+     * of the next user who binds to that port. To prevent this scenario, these sockets are held
+     * open by the system so that they may only be closed by calling {@link #close} or when the user
+     * process exits.
+     */
+    public static final class UdpEncapsulationSocket implements AutoCloseable {
+        private final ParcelFileDescriptor mPfd;
+        private final IIpSecService mService;
+        private int mResourceId = INVALID_RESOURCE_ID;
+        private final int mPort;
+        private final CloseGuard mCloseGuard = CloseGuard.get();
+
+        private UdpEncapsulationSocket(@NonNull IIpSecService service, int port)
+                throws ResourceUnavailableException, IOException {
+            mService = service;
+            try {
+                IpSecUdpEncapResponse result =
+                        mService.openUdpEncapsulationSocket(port, new Binder());
+                switch (result.status) {
+                    case Status.OK:
+                        break;
+                    case Status.RESOURCE_UNAVAILABLE:
+                        throw new ResourceUnavailableException(
+                                "No more Sockets may be allocated by this requester.");
+                    default:
+                        throw new RuntimeException(
+                                "Unknown status returned by IpSecService: " + result.status);
+                }
+                mResourceId = result.resourceId;
+                mPort = result.port;
+                mPfd = result.fileDescriptor;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            mCloseGuard.open("constructor");
+        }
+
+        /** Get the encapsulation socket's file descriptor. */
+        public FileDescriptor getFileDescriptor() {
+            if (mPfd == null) {
+                return null;
+            }
+            return mPfd.getFileDescriptor();
+        }
+
+        /** Get the bound port of the wrapped socket. */
+        public int getPort() {
+            return mPort;
+        }
+
+        /**
+         * Close this socket.
+         *
+         * <p>This closes the wrapped socket. Open encapsulation sockets count against a user's
+         * resource limits, and forgetting to close them eventually will result in {@link
+         * ResourceUnavailableException} being thrown.
+         */
+        @Override
+        public void close() throws IOException {
+            try {
+                mService.closeUdpEncapsulationSocket(mResourceId);
+                mResourceId = INVALID_RESOURCE_ID;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            } catch (Exception e) {
+                // On close we swallow all random exceptions since failure to close is not
+                // actionable by the user.
+                Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
+            } finally {
+                mResourceId = INVALID_RESOURCE_ID;
+                mCloseGuard.close();
+            }
+
+            try {
+                mPfd.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to close UDP Encapsulation Socket with Port= " + mPort);
+                throw e;
+            }
+        }
+
+        /** Check that the socket was closed properly. */
+        @Override
+        protected void finalize() throws Throwable {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+            close();
+        }
+
+        /** @hide */
+        @SystemApi(client = MODULE_LIBRARIES)
+        public int getResourceId() {
+            return mResourceId;
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder()
+                .append("UdpEncapsulationSocket{port=")
+                .append(mPort)
+                .append(",resourceId=")
+                .append(mResourceId)
+                .append("}")
+                .toString();
+        }
+    };
+
+    /**
+     * Open a socket for UDP encapsulation and bind to the given port.
+     *
+     * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket.
+     *
+     * @param port a local UDP port
+     * @return a socket that is bound to the given port
+     * @throws IOException indicating that the socket could not be opened or bound
+     * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open
+     */
+    // Returning a socket in this fashion that has been created and bound by the system
+    // is the only safe way to ensure that a socket is both accessible to the user and
+    // safely usable for Encapsulation without allowing a user to possibly unbind from/close
+    // the port, which could potentially impact the traffic of the next user who binds to that
+    // socket.
+    @NonNull
+    public UdpEncapsulationSocket openUdpEncapsulationSocket(int port)
+            throws IOException, ResourceUnavailableException {
+        /*
+         * Most range checking is done in the service, but this version of the constructor expects
+         * a valid port number, and zero cannot be checked after being passed to the service.
+         */
+        if (port == 0) {
+            throw new IllegalArgumentException("Specified port must be a valid port number!");
+        }
+        try {
+            return new UdpEncapsulationSocket(mService, port);
+        } catch (ServiceSpecificException e) {
+            throw rethrowCheckedExceptionFromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Open a socket for UDP encapsulation.
+     *
+     * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket.
+     *
+     * <p>The local port of the returned socket can be obtained by calling {@link
+     * UdpEncapsulationSocket#getPort()}.
+     *
+     * @return a socket that is bound to a local port
+     * @throws IOException indicating that the socket could not be opened or bound
+     * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open
+     */
+    // Returning a socket in this fashion that has been created and bound by the system
+    // is the only safe way to ensure that a socket is both accessible to the user and
+    // safely usable for Encapsulation without allowing a user to possibly unbind from/close
+    // the port, which could potentially impact the traffic of the next user who binds to that
+    // socket.
+    @NonNull
+    public UdpEncapsulationSocket openUdpEncapsulationSocket()
+            throws IOException, ResourceUnavailableException {
+        try {
+            return new UdpEncapsulationSocket(mService, 0);
+        } catch (ServiceSpecificException e) {
+            throw rethrowCheckedExceptionFromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * This class represents an IpSecTunnelInterface
+     *
+     * <p>IpSecTunnelInterface objects track tunnel interfaces that serve as
+     * local endpoints for IPsec tunnels.
+     *
+     * <p>Creating an IpSecTunnelInterface creates a device to which IpSecTransforms may be
+     * applied to provide IPsec security to packets sent through the tunnel. While a tunnel
+     * cannot be used in standalone mode within Android, the higher layers may use the tunnel
+     * to create Network objects which are accessible to the Android system.
+     * @hide
+     */
+    @SystemApi
+    public static final class IpSecTunnelInterface implements AutoCloseable {
+        private final String mOpPackageName;
+        private final IIpSecService mService;
+        private final InetAddress mRemoteAddress;
+        private final InetAddress mLocalAddress;
+        private final Network mUnderlyingNetwork;
+        private final CloseGuard mCloseGuard = CloseGuard.get();
+        private String mInterfaceName;
+        private int mResourceId = INVALID_RESOURCE_ID;
+
+        /** Get the underlying SPI held by this object. */
+        @NonNull
+        public String getInterfaceName() {
+            return mInterfaceName;
+        }
+
+        /**
+         * Add an address to the IpSecTunnelInterface
+         *
+         * <p>Add an address which may be used as the local inner address for
+         * tunneled traffic.
+         *
+         * @param address the local address for traffic inside the tunnel
+         * @param prefixLen length of the InetAddress prefix
+         * @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 {
+                mService.addAddressToTunnelInterface(
+                        mResourceId, new LinkAddress(address, prefixLen), mOpPackageName);
+            } catch (ServiceSpecificException e) {
+                throw rethrowCheckedExceptionFromServiceSpecificException(e);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Remove an address from the IpSecTunnelInterface
+         *
+         * <p>Remove an address which was previously added to the IpSecTunnelInterface
+         *
+         * @param address to be removed
+         * @param prefixLen length of the InetAddress prefix
+         * @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 {
+                mService.removeAddressFromTunnelInterface(
+                        mResourceId, new LinkAddress(address, prefixLen), mOpPackageName);
+            } catch (ServiceSpecificException e) {
+                throw rethrowCheckedExceptionFromServiceSpecificException(e);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Update the underlying network for this IpSecTunnelInterface.
+         *
+         * <p>This new underlying network will be used for all transforms applied AFTER this call is
+         * complete. Before new {@link IpSecTransform}(s) with matching addresses are applied to
+         * this tunnel interface, traffic will still use the old SA, and be routed on the old
+         * underlying network.
+         *
+         * <p>To migrate IPsec tunnel mode traffic, a caller should:
+         *
+         * <ol>
+         *   <li>Update the IpSecTunnelInterface’s underlying network.
+         *   <li>Apply {@link IpSecTransform}(s) with matching addresses to this
+         *       IpSecTunnelInterface.
+         * </ol>
+         *
+         * @param underlyingNetwork the new {@link Network} that will carry traffic for this tunnel.
+         *     This network MUST never be the network exposing this IpSecTunnelInterface, otherwise
+         *     this method will throw an {@link IllegalArgumentException}. If the
+         *     IpSecTunnelInterface is later added to this network, all outbound traffic will be
+         *     blackholed.
+         */
+        // TODO: b/169171001 Update the documentation when transform migration is supported.
+        // The purpose of making updating network and applying transforms separate is to leave open
+        // the possibility to support lossless migration procedures. To do that, Android platform
+        // will need to support multiple inbound tunnel mode transforms, just like it can support
+        // multiple transport mode transforms.
+        @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+        @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+        public void setUnderlyingNetwork(@NonNull Network underlyingNetwork) throws IOException {
+            try {
+                mService.setNetworkForTunnelInterface(
+                        mResourceId, underlyingNetwork, mOpPackageName);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        private IpSecTunnelInterface(@NonNull Context ctx, @NonNull IIpSecService service,
+                @NonNull InetAddress localAddress, @NonNull InetAddress remoteAddress,
+                @NonNull Network underlyingNetwork)
+                throws ResourceUnavailableException, IOException {
+            mOpPackageName = ctx.getOpPackageName();
+            mService = service;
+            mLocalAddress = localAddress;
+            mRemoteAddress = remoteAddress;
+            mUnderlyingNetwork = underlyingNetwork;
+
+            try {
+                IpSecTunnelInterfaceResponse result =
+                        mService.createTunnelInterface(
+                                localAddress.getHostAddress(),
+                                remoteAddress.getHostAddress(),
+                                underlyingNetwork,
+                                new Binder(),
+                                mOpPackageName);
+                switch (result.status) {
+                    case Status.OK:
+                        break;
+                    case Status.RESOURCE_UNAVAILABLE:
+                        throw new ResourceUnavailableException(
+                                "No more tunnel interfaces may be allocated by this requester.");
+                    default:
+                        throw new RuntimeException(
+                                "Unknown status returned by IpSecService: " + result.status);
+                }
+                mResourceId = result.resourceId;
+                mInterfaceName = result.interfaceName;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            mCloseGuard.open("constructor");
+        }
+
+        /**
+         * Delete an IpSecTunnelInterface
+         *
+         * <p>Calling close will deallocate the IpSecTunnelInterface and all of its system
+         * resources. Any packets bound for this interface either inbound or outbound will
+         * all be lost.
+         */
+        @Override
+        public void close() {
+            try {
+                mService.deleteTunnelInterface(mResourceId, mOpPackageName);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            } catch (Exception e) {
+                // On close we swallow all random exceptions since failure to close is not
+                // actionable by the user.
+                Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
+            } finally {
+                mResourceId = INVALID_RESOURCE_ID;
+                mCloseGuard.close();
+            }
+        }
+
+        /** Check that the Interface was closed properly. */
+        @Override
+        protected void finalize() throws Throwable {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+            close();
+        }
+
+        /** @hide */
+        @VisibleForTesting
+        public int getResourceId() {
+            return mResourceId;
+        }
+
+        @NonNull
+        @Override
+        public String toString() {
+            return new StringBuilder()
+                .append("IpSecTunnelInterface{ifname=")
+                .append(mInterfaceName)
+                .append(",resourceId=")
+                .append(mResourceId)
+                .append("}")
+                .toString();
+        }
+    }
+
+    /**
+     * Create a new IpSecTunnelInterface as a local endpoint for tunneled IPsec traffic.
+     *
+     * <p>An application that creates tunnels is responsible for cleaning up the tunnel when the
+     * underlying network goes away, and the onLost() callback is received.
+     *
+     * @param localAddress The local addres of the tunnel
+     * @param remoteAddress The local addres of the tunnel
+     * @param underlyingNetwork the {@link Network} that will carry traffic for this tunnel.
+     *        This network should almost certainly be a network such as WiFi with an L2 address.
+     * @return a new {@link IpSecManager#IpSecTunnelInterface} with the specified properties
+     * @throws IOException indicating that the socket could not be opened or bound
+     * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open
+     * @hide
+     */
+    @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)
+            throws ResourceUnavailableException, IOException {
+        try {
+            return new IpSecTunnelInterface(
+                    mContext, mService, localAddress, remoteAddress, underlyingNetwork);
+        } catch (ServiceSpecificException e) {
+            throw rethrowCheckedExceptionFromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Apply an active Tunnel Mode IPsec Transform to a {@link IpSecTunnelInterface}, which will
+     * tunnel all traffic for the given direction through the underlying network's interface with
+     * IPsec (applies an outer IP header and IPsec Header to all traffic, and expects an additional
+     * IP header and IPsec Header on all inbound traffic).
+     * <p>Applications should probably not use this API directly.
+     *
+     *
+     * @param tunnel The {@link IpSecManager#IpSecTunnelInterface} that will use the supplied
+     *        transform.
+     * @param direction the direction, {@link DIRECTION_OUT} or {@link #DIRECTION_IN} in which
+     *        the transform will be used.
+     * @param transform an {@link IpSecTransform} created in tunnel mode
+     * @throws IOException indicating that the transform could not be applied due to a lower
+     *         layer failure.
+     * @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 {
+        try {
+            mService.applyTunnelModeTransform(
+                    tunnel.getResourceId(), direction,
+                    transform.getResourceId(), mContext.getOpPackageName());
+        } catch (ServiceSpecificException e) {
+            throw rethrowCheckedExceptionFromServiceSpecificException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Construct an instance of IpSecManager within an application context.
+     *
+     * @param context the application context for this manager
+     * @hide
+     */
+    public IpSecManager(Context ctx, IIpSecService service) {
+        mContext = ctx;
+        mService = checkNotNull(service, "missing service");
+    }
+
+    private static void maybeHandleServiceSpecificException(ServiceSpecificException sse) {
+        // OsConstants are late binding, so switch statements can't be used.
+        if (sse.errorCode == OsConstants.EINVAL) {
+            throw new IllegalArgumentException(sse);
+        } else if (sse.errorCode == OsConstants.EAGAIN) {
+            throw new IllegalStateException(sse);
+        } else if (sse.errorCode == OsConstants.EOPNOTSUPP
+                || sse.errorCode == OsConstants.EPROTONOSUPPORT) {
+            throw new UnsupportedOperationException(sse);
+        }
+    }
+
+    /**
+     * Convert an Errno SSE to the correct Unchecked exception type.
+     *
+     * This method never actually returns.
+     */
+    // package
+    static RuntimeException
+            rethrowUncheckedExceptionFromServiceSpecificException(ServiceSpecificException sse) {
+        maybeHandleServiceSpecificException(sse);
+        throw new RuntimeException(sse);
+    }
+
+    /**
+     * Convert an Errno SSE to the correct Checked or Unchecked exception type.
+     *
+     * This method may throw IOException, or it may throw an unchecked exception; it will never
+     * actually return.
+     */
+    // package
+    static IOException rethrowCheckedExceptionFromServiceSpecificException(
+            ServiceSpecificException sse) throws IOException {
+        // First see if this is an unchecked exception of a type we know.
+        // If so, then we prefer the unchecked (specific) type of exception.
+        maybeHandleServiceSpecificException(sse);
+        // If not, then all we can do is provide the SSE in the form of an IOException.
+        throw new ErrnoException(
+                "IpSec encountered errno=" + sse.errorCode, sse.errorCode).rethrowAsIOException();
+    }
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.aidl
new file mode 100644
index 0000000..6484a00
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 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;
+
+/** @hide */
+parcelable IpSecSpiResponse;
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.java
new file mode 100644
index 0000000..f99e570
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to return an SPI and corresponding status from the IpSecService to an
+ * IpSecManager.SecurityParameterIndex.
+ *
+ * @hide
+ */
+public final class IpSecSpiResponse implements Parcelable {
+    private static final String TAG = "IpSecSpiResponse";
+
+    public final int resourceId;
+    public final int status;
+    public final int spi;
+    // Parcelable Methods
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(status);
+        out.writeInt(resourceId);
+        out.writeInt(spi);
+    }
+
+    public IpSecSpiResponse(int inStatus, int inResourceId, int inSpi) {
+        status = inStatus;
+        resourceId = inResourceId;
+        spi = inSpi;
+    }
+
+    public IpSecSpiResponse(int inStatus) {
+        if (inStatus == IpSecManager.Status.OK) {
+            throw new IllegalArgumentException("Valid status implies other args must be provided");
+        }
+        status = inStatus;
+        resourceId = IpSecManager.INVALID_RESOURCE_ID;
+        spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
+    }
+
+    private IpSecSpiResponse(Parcel in) {
+        status = in.readInt();
+        resourceId = in.readInt();
+        spi = in.readInt();
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<IpSecSpiResponse> CREATOR =
+            new Parcelable.Creator<IpSecSpiResponse>() {
+                public IpSecSpiResponse createFromParcel(Parcel in) {
+                    return new IpSecSpiResponse(in);
+                }
+
+                public IpSecSpiResponse[] newArray(int size) {
+                    return new IpSecSpiResponse[size];
+                }
+            };
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java
new file mode 100644
index 0000000..b48c1fd
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright (C) 2017 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;
+
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+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.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import dalvik.system.CloseGuard;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+
+/**
+ * This class represents a transform, which roughly corresponds to an IPsec Security Association.
+ *
+ * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform}
+ * object encapsulates the properties and state of an IPsec security association. That includes,
+ * but is not limited to, algorithm choice, key material, and allocated system resources.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ *     Internet Protocol</a>
+ */
+public final class IpSecTransform implements AutoCloseable {
+    private static final String TAG = "IpSecTransform";
+
+    /** @hide */
+    public static final int MODE_TRANSPORT = 0;
+
+    /** @hide */
+    public static final int MODE_TUNNEL = 1;
+
+    /** @hide */
+    public static final int ENCAP_NONE = 0;
+
+    /**
+     * IPsec traffic will be encapsulated within UDP, but with 8 zero-value bytes between the UDP
+     * header and payload. This prevents traffic from being interpreted as ESP or IKEv2.
+     *
+     * @hide
+     */
+    public static final int ENCAP_ESPINUDP_NON_IKE = 1;
+
+    /**
+     * IPsec traffic will be encapsulated within UDP as per
+     * <a href="https://tools.ietf.org/html/rfc3948">RFC 3498</a>.
+     *
+     * @hide
+     */
+    public static final int ENCAP_ESPINUDP = 2;
+
+    /** @hide */
+    @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NON_IKE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EncapType {}
+
+    /** @hide */
+    @VisibleForTesting
+    public IpSecTransform(Context context, IpSecConfig config) {
+        mContext = context;
+        mConfig = new IpSecConfig(config);
+        mResourceId = INVALID_RESOURCE_ID;
+    }
+
+    private IIpSecService getIpSecService() {
+        IBinder b = ServiceManager.getService(android.content.Context.IPSEC_SERVICE);
+        if (b == null) {
+            throw new RemoteException("Failed to connect to IpSecService")
+                    .rethrowAsRuntimeException();
+        }
+
+        return IIpSecService.Stub.asInterface(b);
+    }
+
+    /**
+     * Checks the result status and throws an appropriate exception if the status is not Status.OK.
+     */
+    private void checkResultStatus(int status)
+            throws IOException, IpSecManager.ResourceUnavailableException,
+                    IpSecManager.SpiUnavailableException {
+        switch (status) {
+            case IpSecManager.Status.OK:
+                return;
+                // TODO: Pass Error string back from bundle so that errors can be more specific
+            case IpSecManager.Status.RESOURCE_UNAVAILABLE:
+                throw new IpSecManager.ResourceUnavailableException(
+                        "Failed to allocate a new IpSecTransform");
+            case IpSecManager.Status.SPI_UNAVAILABLE:
+                Log.wtf(TAG, "Attempting to use an SPI that was somehow not reserved");
+                // Fall through
+            default:
+                throw new IllegalStateException(
+                        "Failed to Create a Transform with status code " + status);
+        }
+    }
+
+    private IpSecTransform activate()
+            throws IOException, IpSecManager.ResourceUnavailableException,
+                    IpSecManager.SpiUnavailableException {
+        synchronized (this) {
+            try {
+                IIpSecService svc = getIpSecService();
+                IpSecTransformResponse result = svc.createTransform(
+                        mConfig, new Binder(), mContext.getOpPackageName());
+                int status = result.status;
+                checkResultStatus(status);
+                mResourceId = result.resourceId;
+                Log.d(TAG, "Added Transform with Id " + mResourceId);
+                mCloseGuard.open("build");
+            } catch (ServiceSpecificException e) {
+                throw IpSecManager.rethrowUncheckedExceptionFromServiceSpecificException(e);
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        }
+
+        return this;
+    }
+
+    /**
+     * Standard equals.
+     */
+    public boolean equals(@Nullable Object other) {
+        if (this == other) return true;
+        if (!(other instanceof IpSecTransform)) return false;
+        final IpSecTransform rhs = (IpSecTransform) other;
+        return getConfig().equals(rhs.getConfig()) && mResourceId == rhs.mResourceId;
+    }
+
+    /**
+     * Deactivate this {@code IpSecTransform} and free allocated resources.
+     *
+     * <p>Deactivating a transform while it is still applied to a socket will result in errors on
+     * that socket. Make sure to remove transforms by calling {@link
+     * IpSecManager#removeTransportModeTransforms}. Note, removing an {@code IpSecTransform} from a
+     * socket will not deactivate it (because one transform may be applied to multiple sockets).
+     *
+     * <p>It is safe to call this method on a transform that has already been deactivated.
+     */
+    public void close() {
+        Log.d(TAG, "Removing Transform with Id " + mResourceId);
+
+        // Always safe to attempt cleanup
+        if (mResourceId == INVALID_RESOURCE_ID) {
+            mCloseGuard.close();
+            return;
+        }
+        try {
+            IIpSecService svc = getIpSecService();
+            svc.deleteTransform(mResourceId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        } catch (Exception e) {
+            // On close we swallow all random exceptions since failure to close is not
+            // actionable by the user.
+            Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
+        } finally {
+            mResourceId = INVALID_RESOURCE_ID;
+            mCloseGuard.close();
+        }
+    }
+
+    /** Check that the transform was closed properly. */
+    @Override
+    protected void finalize() throws Throwable {
+        if (mCloseGuard != null) {
+            mCloseGuard.warnIfOpen();
+        }
+        close();
+    }
+
+    /* Package */
+    IpSecConfig getConfig() {
+        return mConfig;
+    }
+
+    private final IpSecConfig mConfig;
+    private int mResourceId;
+    private final Context mContext;
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+
+    /** @hide */
+    @VisibleForTesting
+    public int getResourceId() {
+        return mResourceId;
+    }
+
+    /**
+     * A callback class to provide status information regarding a NAT-T keepalive session
+     *
+     * <p>Use this callback to receive status information regarding a NAT-T keepalive session
+     * by registering it when calling {@link #startNattKeepalive}.
+     *
+     * @hide
+     */
+    public static class NattKeepaliveCallback {
+        /** The specified {@code Network} is not connected. */
+        public static final int ERROR_INVALID_NETWORK = 1;
+        /** The hardware does not support this request. */
+        public static final int ERROR_HARDWARE_UNSUPPORTED = 2;
+        /** The hardware returned an error. */
+        public static final int ERROR_HARDWARE_ERROR = 3;
+
+        /** The requested keepalive was successfully started. */
+        public void onStarted() {}
+        /** The keepalive was successfully stopped. */
+        public void onStopped() {}
+        /** An error occurred. */
+        public void onError(int error) {}
+    }
+
+    /** This class is used to build {@link IpSecTransform} objects. */
+    public static class Builder {
+        private Context mContext;
+        private IpSecConfig mConfig;
+
+        /**
+         * Set the encryption algorithm.
+         *
+         * <p>Encryption is mutually exclusive with authenticated encryption.
+         *
+         * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
+         */
+        @NonNull
+        public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) {
+            // TODO: throw IllegalArgumentException if algo is not an encryption algorithm.
+            Preconditions.checkNotNull(algo);
+            mConfig.setEncryption(algo);
+            return this;
+        }
+
+        /**
+         * Set the authentication (integrity) algorithm.
+         *
+         * <p>Authentication is mutually exclusive with authenticated encryption.
+         *
+         * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
+         */
+        @NonNull
+        public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) {
+            // TODO: throw IllegalArgumentException if algo is not an authentication algorithm.
+            Preconditions.checkNotNull(algo);
+            mConfig.setAuthentication(algo);
+            return this;
+        }
+
+        /**
+         * Set the authenticated encryption algorithm.
+         *
+         * <p>The Authenticated Encryption (AE) class of algorithms are also known as
+         * Authenticated Encryption with Associated Data (AEAD) algorithms, or Combined mode
+         * algorithms (as referred to in
+         * <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>).
+         *
+         * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+         *
+         * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to
+         *     be applied.
+         */
+        @NonNull
+        public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) {
+            Preconditions.checkNotNull(algo);
+            mConfig.setAuthenticatedEncryption(algo);
+            return this;
+        }
+
+        /**
+         * Add UDP encapsulation to an IPv4 transform.
+         *
+         * <p>This allows IPsec traffic to pass through a NAT.
+         *
+         * @see <a href="https://tools.ietf.org/html/rfc3948">RFC 3948, UDP Encapsulation of IPsec
+         *     ESP Packets</a>
+         * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.23">RFC 7296 section 2.23,
+         *     NAT Traversal of IKEv2</a>
+         * @param localSocket a socket for sending and receiving encapsulated traffic
+         * @param remotePort the UDP port number of the remote host that will send and receive
+         *     encapsulated traffic. In the case of IKEv2, this should be port 4500.
+         */
+        @NonNull
+        public IpSecTransform.Builder setIpv4Encapsulation(
+                @NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
+            Preconditions.checkNotNull(localSocket);
+            mConfig.setEncapType(ENCAP_ESPINUDP);
+            if (localSocket.getResourceId() == INVALID_RESOURCE_ID) {
+                throw new IllegalArgumentException("Invalid UdpEncapsulationSocket");
+            }
+            mConfig.setEncapSocketResourceId(localSocket.getResourceId());
+            mConfig.setEncapRemotePort(remotePort);
+            return this;
+        }
+
+        /**
+         * Build a transport mode {@link IpSecTransform}.
+         *
+         * <p>This builds and activates a transport mode transform. Note that an active transform
+         * will not affect any network traffic until it has been applied to one or more sockets.
+         *
+         * @see IpSecManager#applyTransportModeTransform
+         * @param sourceAddress the source {@code InetAddress} of traffic on sockets that will use
+         *     this transform; this address must belong to the Network used by all sockets that
+         *     utilize this transform; if provided, then only traffic originating from the
+         *     specified source address will be processed.
+         * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
+         *     traffic
+         * @throws IllegalArgumentException indicating that a particular combination of transform
+         *     properties is invalid
+         * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms
+         *     are active
+         * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
+         *     collides with an existing transform
+         * @throws IOException indicating other errors
+         */
+        @NonNull
+        public IpSecTransform buildTransportModeTransform(
+                @NonNull InetAddress sourceAddress,
+                @NonNull IpSecManager.SecurityParameterIndex spi)
+                throws IpSecManager.ResourceUnavailableException,
+                        IpSecManager.SpiUnavailableException, IOException {
+            Preconditions.checkNotNull(sourceAddress);
+            Preconditions.checkNotNull(spi);
+            if (spi.getResourceId() == INVALID_RESOURCE_ID) {
+                throw new IllegalArgumentException("Invalid SecurityParameterIndex");
+            }
+            mConfig.setMode(MODE_TRANSPORT);
+            mConfig.setSourceAddress(sourceAddress.getHostAddress());
+            mConfig.setSpiResourceId(spi.getResourceId());
+            // FIXME: modifying a builder after calling build can change the built transform.
+            return new IpSecTransform(mContext, mConfig).activate();
+        }
+
+        /**
+         * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some
+         * parameters have interdependencies that are checked at build time.
+         *
+         * @param sourceAddress the {@link InetAddress} that provides the source address for this
+         *     IPsec tunnel. This is almost certainly an address belonging to the {@link Network}
+         *     that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}.
+         * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
+         *     traffic
+         * @throws IllegalArgumentException indicating that a particular combination of transform
+         *     properties is invalid.
+         * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms
+         *     are active
+         * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
+         *     collides with an existing transform
+         * @throws IOException indicating other errors
+         * @hide
+         */
+        @SystemApi
+        @NonNull
+        @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+        @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+        public IpSecTransform buildTunnelModeTransform(
+                @NonNull InetAddress sourceAddress,
+                @NonNull IpSecManager.SecurityParameterIndex spi)
+                throws IpSecManager.ResourceUnavailableException,
+                        IpSecManager.SpiUnavailableException, IOException {
+            Preconditions.checkNotNull(sourceAddress);
+            Preconditions.checkNotNull(spi);
+            if (spi.getResourceId() == INVALID_RESOURCE_ID) {
+                throw new IllegalArgumentException("Invalid SecurityParameterIndex");
+            }
+            mConfig.setMode(MODE_TUNNEL);
+            mConfig.setSourceAddress(sourceAddress.getHostAddress());
+            mConfig.setSpiResourceId(spi.getResourceId());
+            return new IpSecTransform(mContext, mConfig).activate();
+        }
+
+        /**
+         * Create a new IpSecTransform.Builder.
+         *
+         * @param context current context
+         */
+        public Builder(@NonNull Context context) {
+            Preconditions.checkNotNull(context);
+            mContext = context;
+            mConfig = new IpSecConfig();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder()
+            .append("IpSecTransform{resourceId=")
+            .append(mResourceId)
+            .append("}")
+            .toString();
+    }
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.aidl
new file mode 100644
index 0000000..546230d
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 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;
+
+/** @hide */
+parcelable IpSecTransformResponse;
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.java
new file mode 100644
index 0000000..a384889
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to return an IpSecTransform resource Id and and corresponding status from the
+ * IpSecService to an IpSecTransform object.
+ *
+ * @hide
+ */
+public final class IpSecTransformResponse implements Parcelable {
+    private static final String TAG = "IpSecTransformResponse";
+
+    public final int resourceId;
+    public final int status;
+    // Parcelable Methods
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(status);
+        out.writeInt(resourceId);
+    }
+
+    public IpSecTransformResponse(int inStatus) {
+        if (inStatus == IpSecManager.Status.OK) {
+            throw new IllegalArgumentException("Valid status implies other args must be provided");
+        }
+        status = inStatus;
+        resourceId = IpSecManager.INVALID_RESOURCE_ID;
+    }
+
+    public IpSecTransformResponse(int inStatus, int inResourceId) {
+        status = inStatus;
+        resourceId = inResourceId;
+    }
+
+    private IpSecTransformResponse(Parcel in) {
+        status = in.readInt();
+        resourceId = in.readInt();
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<IpSecTransformResponse> CREATOR =
+            new Parcelable.Creator<IpSecTransformResponse>() {
+                public IpSecTransformResponse createFromParcel(Parcel in) {
+                    return new IpSecTransformResponse(in);
+                }
+
+                public IpSecTransformResponse[] newArray(int size) {
+                    return new IpSecTransformResponse[size];
+                }
+            };
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl
new file mode 100644
index 0000000..7239221
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+/** @hide */
+parcelable IpSecTunnelInterfaceResponse;
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java
new file mode 100644
index 0000000..e3411e0
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to return an IpSecTunnelInterface resource Id and and corresponding status
+ * from the IpSecService to an IpSecTunnelInterface object.
+ *
+ * @hide
+ */
+public final class IpSecTunnelInterfaceResponse implements Parcelable {
+    private static final String TAG = "IpSecTunnelInterfaceResponse";
+
+    public final int resourceId;
+    public final String interfaceName;
+    public final int status;
+    // Parcelable Methods
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(status);
+        out.writeInt(resourceId);
+        out.writeString(interfaceName);
+    }
+
+    public IpSecTunnelInterfaceResponse(int inStatus) {
+        if (inStatus == IpSecManager.Status.OK) {
+            throw new IllegalArgumentException("Valid status implies other args must be provided");
+        }
+        status = inStatus;
+        resourceId = IpSecManager.INVALID_RESOURCE_ID;
+        interfaceName = "";
+    }
+
+    public IpSecTunnelInterfaceResponse(int inStatus, int inResourceId, String inInterfaceName) {
+        status = inStatus;
+        resourceId = inResourceId;
+        interfaceName = inInterfaceName;
+    }
+
+    private IpSecTunnelInterfaceResponse(Parcel in) {
+        status = in.readInt();
+        resourceId = in.readInt();
+        interfaceName = in.readString();
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<IpSecTunnelInterfaceResponse> CREATOR =
+            new Parcelable.Creator<IpSecTunnelInterfaceResponse>() {
+                public IpSecTunnelInterfaceResponse createFromParcel(Parcel in) {
+                    return new IpSecTunnelInterfaceResponse(in);
+                }
+
+                public IpSecTunnelInterfaceResponse[] newArray(int size) {
+                    return new IpSecTunnelInterfaceResponse[size];
+                }
+            };
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.aidl
new file mode 100644
index 0000000..5e451f3
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 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;
+
+/** @hide */
+parcelable IpSecUdpEncapResponse;
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
new file mode 100644
index 0000000..4e7ba9b
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * This class is used to return a UDP Socket and corresponding status from the IpSecService to an
+ * IpSecManager.UdpEncapsulationSocket.
+ *
+ * @hide
+ */
+public final class IpSecUdpEncapResponse implements Parcelable {
+    private static final String TAG = "IpSecUdpEncapResponse";
+
+    public final int resourceId;
+    public final int port;
+    public final int status;
+    // There is a weird asymmetry with FileDescriptor: you can write a FileDescriptor
+    // but you read a ParcelFileDescriptor. To circumvent this, when we receive a FD
+    // from the user, we immediately create a ParcelFileDescriptor DUP, which we invalidate
+    // on writeParcel() by setting the flag to do close-on-write.
+    // TODO: tests to ensure this doesn't leak
+    public final ParcelFileDescriptor fileDescriptor;
+
+    // Parcelable Methods
+
+    @Override
+    public int describeContents() {
+        return (fileDescriptor != null) ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(status);
+        out.writeInt(resourceId);
+        out.writeInt(port);
+        out.writeParcelable(fileDescriptor, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+    }
+
+    public IpSecUdpEncapResponse(int inStatus) {
+        if (inStatus == IpSecManager.Status.OK) {
+            throw new IllegalArgumentException("Valid status implies other args must be provided");
+        }
+        status = inStatus;
+        resourceId = IpSecManager.INVALID_RESOURCE_ID;
+        port = -1;
+        fileDescriptor = null; // yes I know it's redundant, but readability
+    }
+
+    public IpSecUdpEncapResponse(int inStatus, int inResourceId, int inPort, FileDescriptor inFd)
+            throws IOException {
+        if (inStatus == IpSecManager.Status.OK && inFd == null) {
+            throw new IllegalArgumentException("Valid status implies FD must be non-null");
+        }
+        status = inStatus;
+        resourceId = inResourceId;
+        port = inPort;
+        fileDescriptor = (status == IpSecManager.Status.OK) ? ParcelFileDescriptor.dup(inFd) : null;
+    }
+
+    private IpSecUdpEncapResponse(Parcel in) {
+        status = in.readInt();
+        resourceId = in.readInt();
+        port = in.readInt();
+        fileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<IpSecUdpEncapResponse> CREATOR =
+            new Parcelable.Creator<IpSecUdpEncapResponse>() {
+                public IpSecUdpEncapResponse createFromParcel(Parcel in) {
+                    return new IpSecUdpEncapResponse(in);
+                }
+
+                public IpSecUdpEncapResponse[] newArray(int size) {
+                    return new IpSecUdpEncapResponse[size];
+                }
+            };
+}
diff --git a/packages/ConnectivityT/service/Android.bp b/packages/ConnectivityT/service/Android.bp
index 6a64910..7b88176 100644
--- a/packages/ConnectivityT/service/Android.bp
+++ b/packages/ConnectivityT/service/Android.bp
@@ -48,16 +48,28 @@
     ],
 }
 
+// IpSec related libraries.
+
+filegroup {
+    name: "services.connectivity-ipsec-sources",
+    srcs: [
+        "src/com/android/server/IpSecService.java",
+    ],
+    path: "src",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
 // Connectivity-T common libraries.
 
 filegroup {
     name: "services.connectivity-tiramisu-sources",
     srcs: [
+        ":services.connectivity-ipsec-sources",
         ":services.connectivity-netstats-sources",
         ":services.connectivity-nsd-sources",
     ],
     path: "src",
-    visibility: [
-        "//frameworks/base/services/core",
-    ],
-}
\ No newline at end of file
+    visibility: ["//frameworks/base/services/core"],
+}
diff --git a/packages/ConnectivityT/service/src/com/android/server/IpSecService.java b/packages/ConnectivityT/service/src/com/android/server/IpSecService.java
new file mode 100644
index 0000000..aeb8143
--- /dev/null
+++ b/packages/ConnectivityT/service/src/com/android/server/IpSecService.java
@@ -0,0 +1,1917 @@
+/*
+ * Copyright (C) 2017 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.Manifest.permission.DUMP;
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.AF_UNSPEC;
+import static android.system.OsConstants.EINVAL;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.IIpSecService;
+import android.net.INetd;
+import android.net.InetAddresses;
+import android.net.IpSecAlgorithm;
+import android.net.IpSecConfig;
+import android.net.IpSecManager;
+import android.net.IpSecSpiResponse;
+import android.net.IpSecTransform;
+import android.net.IpSecTransformResponse;
+import android.net.IpSecTunnelInterfaceResponse;
+import android.net.IpSecUdpEncapResponse;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.TrafficStats;
+import android.net.util.NetdService;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Range;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.net.module.util.BinderUtils;
+import com.android.net.module.util.NetdUtils;
+import com.android.net.module.util.PermissionUtils;
+
+import libcore.io.IoUtils;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A service to manage multiple clients that want to access the IpSec API. The service is
+ * responsible for maintaining a list of clients and managing the resources (and related quotas)
+ * that each of them own.
+ *
+ * <p>Synchronization in IpSecService is done on all entrypoints due to potential race conditions at
+ * the kernel/xfrm level. Further, this allows the simplifying assumption to be made that only one
+ * thread is ever running at a time.
+ *
+ * @hide
+ */
+public class IpSecService extends IIpSecService.Stub {
+    private static final String TAG = "IpSecService";
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final String NETD_SERVICE_NAME = "netd";
+    private static final int[] ADDRESS_FAMILIES =
+            new int[] {OsConstants.AF_INET, OsConstants.AF_INET6};
+
+    private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms
+    private static final InetAddress INADDR_ANY;
+
+    @VisibleForTesting static final int MAX_PORT_BIND_ATTEMPTS = 10;
+
+    static {
+        try {
+            INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
+        } catch (UnknownHostException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    static final int FREE_PORT_MIN = 1024; // ports 1-1023 are reserved
+    static final int PORT_MAX = 0xFFFF; // ports are an unsigned 16-bit integer
+
+    /* Binder context for this service */
+    private final Context mContext;
+
+    /**
+     * The next non-repeating global ID for tracking resources between users, this service, and
+     * kernel data structures. Accessing this variable is not thread safe, so it is only read or
+     * modified within blocks synchronized on IpSecService.this. We want to avoid -1
+     * (INVALID_RESOURCE_ID) and 0 (we probably forgot to initialize it).
+     */
+    @GuardedBy("IpSecService.this")
+    private int mNextResourceId = 1;
+
+    interface IpSecServiceConfiguration {
+        INetd getNetdInstance() throws RemoteException;
+
+        static IpSecServiceConfiguration GETSRVINSTANCE =
+                new IpSecServiceConfiguration() {
+                    @Override
+                    public INetd getNetdInstance() throws RemoteException {
+                        final INetd netd = NetdService.getInstance();
+                        if (netd == null) {
+                            throw new RemoteException("Failed to Get Netd Instance");
+                        }
+                        return netd;
+                    }
+                };
+    }
+
+    private final IpSecServiceConfiguration mSrvConfig;
+    final UidFdTagger mUidFdTagger;
+
+    /**
+     * Interface for user-reference and kernel-resource cleanup.
+     *
+     * <p>This interface must be implemented for a resource to be reference counted.
+     */
+    @VisibleForTesting
+    public interface IResource {
+        /**
+         * Invalidates a IResource object, ensuring it is invalid for the purposes of allocating new
+         * objects dependent on it.
+         *
+         * <p>Implementations of this method are expected to remove references to the IResource
+         * object from the IpSecService's tracking arrays. The removal from the arrays ensures that
+         * the resource is considered invalid for user access or allocation or use in other
+         * resources.
+         *
+         * <p>References to the IResource object may be held by other RefcountedResource objects,
+         * and as such, the underlying resources and quota may not be cleaned up.
+         */
+        void invalidate() throws RemoteException;
+
+        /**
+         * Releases underlying resources and related quotas.
+         *
+         * <p>Implementations of this method are expected to remove all system resources that are
+         * tracked by the IResource object. Due to other RefcountedResource objects potentially
+         * having references to the IResource object, freeUnderlyingResources may not always be
+         * called from releaseIfUnreferencedRecursively().
+         */
+        void freeUnderlyingResources() throws RemoteException;
+    }
+
+    /**
+     * RefcountedResource manages references and dependencies in an exclusively acyclic graph.
+     *
+     * <p>RefcountedResource implements both explicit and implicit resource management. Creating a
+     * RefcountedResource object creates an explicit reference that must be freed by calling
+     * userRelease(). Additionally, adding this object as a child of another RefcountedResource
+     * object will add an implicit reference.
+     *
+     * <p>Resources are cleaned up when all references, both implicit and explicit, are released
+     * (ie, when userRelease() is called and when all parents have called releaseReference() on this
+     * object.)
+     */
+    @VisibleForTesting
+    public class RefcountedResource<T extends IResource> implements IBinder.DeathRecipient {
+        private final T mResource;
+        private final List<RefcountedResource> mChildren;
+        int mRefCount = 1; // starts at 1 for user's reference.
+        IBinder mBinder;
+
+        RefcountedResource(T resource, IBinder binder, RefcountedResource... children) {
+            synchronized (IpSecService.this) {
+                this.mResource = resource;
+                this.mChildren = new ArrayList<>(children.length);
+                this.mBinder = binder;
+
+                for (RefcountedResource child : children) {
+                    mChildren.add(child);
+                    child.mRefCount++;
+                }
+
+                try {
+                    mBinder.linkToDeath(this, 0);
+                } catch (RemoteException e) {
+                    binderDied();
+                    e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+        /**
+         * If the Binder object dies, this function is called to free the system resources that are
+         * being tracked by this record and to subsequently release this record for garbage
+         * collection
+         */
+        @Override
+        public void binderDied() {
+            synchronized (IpSecService.this) {
+                try {
+                    userRelease();
+                } catch (Exception e) {
+                    Log.e(TAG, "Failed to release resource: " + e);
+                }
+            }
+        }
+
+        public T getResource() {
+            return mResource;
+        }
+
+        /**
+         * Unlinks from binder and performs IpSecService resource cleanup (removes from resource
+         * arrays)
+         *
+         * <p>If this method has been previously called, the RefcountedResource's binder field will
+         * be null, and the method will return without performing the cleanup a second time.
+         *
+         * <p>Note that calling this function does not imply that kernel resources will be freed at
+         * this time, or that the related quota will be returned. Such actions will only be
+         * performed upon the reference count reaching zero.
+         */
+        @GuardedBy("IpSecService.this")
+        public void userRelease() throws RemoteException {
+            // Prevent users from putting reference counts into a bad state by calling
+            // userRelease() multiple times.
+            if (mBinder == null) {
+                return;
+            }
+
+            mBinder.unlinkToDeath(this, 0);
+            mBinder = null;
+
+            mResource.invalidate();
+
+            releaseReference();
+        }
+
+        /**
+         * Removes a reference to this resource. If the resultant reference count is zero, the
+         * underlying resources are freed, and references to all child resources are also dropped
+         * recursively (resulting in them freeing their resources and children, etcetera)
+         *
+         * <p>This method also sets the reference count to an invalid value (-1) to signify that it
+         * has been fully released. Any subsequent calls to this method will result in an
+         * IllegalStateException being thrown due to resource already having been previously
+         * released
+         */
+        @VisibleForTesting
+        @GuardedBy("IpSecService.this")
+        public void releaseReference() throws RemoteException {
+            mRefCount--;
+
+            if (mRefCount > 0) {
+                return;
+            } else if (mRefCount < 0) {
+                throw new IllegalStateException(
+                        "Invalid operation - resource has already been released.");
+            }
+
+            // Cleanup own resources
+            mResource.freeUnderlyingResources();
+
+            // Cleanup child resources as needed
+            for (RefcountedResource<? extends IResource> child : mChildren) {
+                child.releaseReference();
+            }
+
+            // Enforce that resource cleanup can only be called once
+            // By decrementing the refcount (from 0 to -1), the next call will throw an
+            // IllegalStateException - it has already been released fully.
+            mRefCount--;
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder()
+                    .append("{mResource=")
+                    .append(mResource)
+                    .append(", mRefCount=")
+                    .append(mRefCount)
+                    .append(", mChildren=")
+                    .append(mChildren)
+                    .append("}")
+                    .toString();
+        }
+    }
+
+    /**
+     * Very simple counting class that looks much like a counting semaphore
+     *
+     * <p>This class is not thread-safe, and expects that that users of this class will ensure
+     * synchronization and thread safety by holding the IpSecService.this instance lock.
+     */
+    @VisibleForTesting
+    static class ResourceTracker {
+        private final int mMax;
+        int mCurrent;
+
+        ResourceTracker(int max) {
+            mMax = max;
+            mCurrent = 0;
+        }
+
+        boolean isAvailable() {
+            return (mCurrent < mMax);
+        }
+
+        void take() {
+            if (!isAvailable()) {
+                Log.wtf(TAG, "Too many resources allocated!");
+            }
+            mCurrent++;
+        }
+
+        void give() {
+            if (mCurrent <= 0) {
+                Log.wtf(TAG, "We've released this resource too many times");
+            }
+            mCurrent--;
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder()
+                    .append("{mCurrent=")
+                    .append(mCurrent)
+                    .append(", mMax=")
+                    .append(mMax)
+                    .append("}")
+                    .toString();
+        }
+    }
+
+    @VisibleForTesting
+    static final class UserRecord {
+        /* Maximum number of each type of resource that a single UID may possess */
+
+        // Up to 4 active VPNs/IWLAN with potential soft handover.
+        public static final int MAX_NUM_TUNNEL_INTERFACES = 8;
+        public static final int MAX_NUM_ENCAP_SOCKETS = 16;
+
+        // SPIs and Transforms are both cheap, and are 1:1 correlated.
+        public static final int MAX_NUM_TRANSFORMS = 64;
+        public static final int MAX_NUM_SPIS = 64;
+
+        /**
+         * Store each of the OwnedResource types in an (thinly wrapped) sparse array for indexing
+         * and explicit (user) reference management.
+         *
+         * <p>These are stored in separate arrays to improve debuggability and dump output clarity.
+         *
+         * <p>Resources are removed from this array when the user releases their explicit reference
+         * by calling one of the releaseResource() methods.
+         */
+        final RefcountedResourceArray<SpiRecord> mSpiRecords =
+                new RefcountedResourceArray<>(SpiRecord.class.getSimpleName());
+        final RefcountedResourceArray<TransformRecord> mTransformRecords =
+                new RefcountedResourceArray<>(TransformRecord.class.getSimpleName());
+        final RefcountedResourceArray<EncapSocketRecord> mEncapSocketRecords =
+                new RefcountedResourceArray<>(EncapSocketRecord.class.getSimpleName());
+        final RefcountedResourceArray<TunnelInterfaceRecord> mTunnelInterfaceRecords =
+                new RefcountedResourceArray<>(TunnelInterfaceRecord.class.getSimpleName());
+
+        /**
+         * Trackers for quotas for each of the OwnedResource types.
+         *
+         * <p>These trackers are separate from the resource arrays, since they are incremented and
+         * decremented at different points in time. Specifically, quota is only returned upon final
+         * resource deallocation (after all explicit and implicit references are released). Note
+         * that it is possible that calls to releaseResource() will not return the used quota if
+         * there are other resources that depend on (are parents of) the resource being released.
+         */
+        final ResourceTracker mSpiQuotaTracker = new ResourceTracker(MAX_NUM_SPIS);
+        final ResourceTracker mTransformQuotaTracker = new ResourceTracker(MAX_NUM_TRANSFORMS);
+        final ResourceTracker mSocketQuotaTracker = new ResourceTracker(MAX_NUM_ENCAP_SOCKETS);
+        final ResourceTracker mTunnelQuotaTracker = new ResourceTracker(MAX_NUM_TUNNEL_INTERFACES);
+
+        void removeSpiRecord(int resourceId) {
+            mSpiRecords.remove(resourceId);
+        }
+
+        void removeTransformRecord(int resourceId) {
+            mTransformRecords.remove(resourceId);
+        }
+
+        void removeTunnelInterfaceRecord(int resourceId) {
+            mTunnelInterfaceRecords.remove(resourceId);
+        }
+
+        void removeEncapSocketRecord(int resourceId) {
+            mEncapSocketRecords.remove(resourceId);
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder()
+                    .append("{mSpiQuotaTracker=")
+                    .append(mSpiQuotaTracker)
+                    .append(", mTransformQuotaTracker=")
+                    .append(mTransformQuotaTracker)
+                    .append(", mSocketQuotaTracker=")
+                    .append(mSocketQuotaTracker)
+                    .append(", mTunnelQuotaTracker=")
+                    .append(mTunnelQuotaTracker)
+                    .append(", mSpiRecords=")
+                    .append(mSpiRecords)
+                    .append(", mTransformRecords=")
+                    .append(mTransformRecords)
+                    .append(", mEncapSocketRecords=")
+                    .append(mEncapSocketRecords)
+                    .append(", mTunnelInterfaceRecords=")
+                    .append(mTunnelInterfaceRecords)
+                    .append("}")
+                    .toString();
+        }
+    }
+
+    /**
+     * This class is not thread-safe, and expects that that users of this class will ensure
+     * synchronization and thread safety by holding the IpSecService.this instance lock.
+     */
+    @VisibleForTesting
+    static final class UserResourceTracker {
+        private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
+
+        /** Lazy-initialization/getter that populates or retrieves the UserRecord as needed */
+        public UserRecord getUserRecord(int uid) {
+            checkCallerUid(uid);
+
+            UserRecord r = mUserRecords.get(uid);
+            if (r == null) {
+                r = new UserRecord();
+                mUserRecords.put(uid, r);
+            }
+            return r;
+        }
+
+        /** Safety method; guards against access of other user's UserRecords */
+        private void checkCallerUid(int uid) {
+            if (uid != Binder.getCallingUid() && Process.SYSTEM_UID != Binder.getCallingUid()) {
+                throw new SecurityException("Attempted access of unowned resources");
+            }
+        }
+
+        @Override
+        public String toString() {
+            return mUserRecords.toString();
+        }
+    }
+
+    @VisibleForTesting final UserResourceTracker mUserResourceTracker = new UserResourceTracker();
+
+    /**
+     * The OwnedResourceRecord class provides a facility to cleanly and reliably track system
+     * resources. It relies on a provided resourceId that should uniquely identify the kernel
+     * resource. To use this class, the user should implement the invalidate() and
+     * freeUnderlyingResources() methods that are responsible for cleaning up IpSecService resource
+     * tracking arrays and kernel resources, respectively.
+     *
+     * <p>This class associates kernel resources with the UID that owns and controls them.
+     */
+    private abstract class OwnedResourceRecord implements IResource {
+        final int pid;
+        final int uid;
+        protected final int mResourceId;
+
+        OwnedResourceRecord(int resourceId) {
+            super();
+            if (resourceId == INVALID_RESOURCE_ID) {
+                throw new IllegalArgumentException("Resource ID must not be INVALID_RESOURCE_ID");
+            }
+            mResourceId = resourceId;
+            pid = Binder.getCallingPid();
+            uid = Binder.getCallingUid();
+
+            getResourceTracker().take();
+        }
+
+        @Override
+        public abstract void invalidate() throws RemoteException;
+
+        /** Convenience method; retrieves the user resource record for the stored UID. */
+        protected UserRecord getUserRecord() {
+            return mUserResourceTracker.getUserRecord(uid);
+        }
+
+        @Override
+        public abstract void freeUnderlyingResources() throws RemoteException;
+
+        /** Get the resource tracker for this resource */
+        protected abstract ResourceTracker getResourceTracker();
+
+        @Override
+        public String toString() {
+            return new StringBuilder()
+                    .append("{mResourceId=")
+                    .append(mResourceId)
+                    .append(", pid=")
+                    .append(pid)
+                    .append(", uid=")
+                    .append(uid)
+                    .append("}")
+                    .toString();
+        }
+    };
+
+    /**
+     * Thin wrapper over SparseArray to ensure resources exist, and simplify generic typing.
+     *
+     * <p>RefcountedResourceArray prevents null insertions, and throws an IllegalArgumentException
+     * if a key is not found during a retrieval process.
+     */
+    static class RefcountedResourceArray<T extends IResource> {
+        SparseArray<RefcountedResource<T>> mArray = new SparseArray<>();
+        private final String mTypeName;
+
+        public RefcountedResourceArray(String typeName) {
+            this.mTypeName = typeName;
+        }
+
+        /**
+         * Accessor method to get inner resource object.
+         *
+         * @throws IllegalArgumentException if no resource with provided key is found.
+         */
+        T getResourceOrThrow(int key) {
+            return getRefcountedResourceOrThrow(key).getResource();
+        }
+
+        /**
+         * Accessor method to get reference counting wrapper.
+         *
+         * @throws IllegalArgumentException if no resource with provided key is found.
+         */
+        RefcountedResource<T> getRefcountedResourceOrThrow(int key) {
+            RefcountedResource<T> resource = mArray.get(key);
+            if (resource == null) {
+                throw new IllegalArgumentException(
+                        String.format("No such %s found for given id: %d", mTypeName, key));
+            }
+
+            return resource;
+        }
+
+        void put(int key, RefcountedResource<T> obj) {
+            Objects.requireNonNull(obj, "Null resources cannot be added");
+            mArray.put(key, obj);
+        }
+
+        void remove(int key) {
+            mArray.remove(key);
+        }
+
+        @Override
+        public String toString() {
+            return mArray.toString();
+        }
+    }
+
+    /**
+     * Tracks an SA in the kernel, and manages cleanup paths. Once a TransformRecord is
+     * created, the SpiRecord that originally tracked the SAs will reliquish the
+     * responsibility of freeing the underlying SA to this class via the mOwnedByTransform flag.
+     */
+    private final class TransformRecord extends OwnedResourceRecord {
+        private final IpSecConfig mConfig;
+        private final SpiRecord mSpi;
+        private final EncapSocketRecord mSocket;
+
+        TransformRecord(
+                int resourceId, IpSecConfig config, SpiRecord spi, EncapSocketRecord socket) {
+            super(resourceId);
+            mConfig = config;
+            mSpi = spi;
+            mSocket = socket;
+
+            spi.setOwnedByTransform();
+        }
+
+        public IpSecConfig getConfig() {
+            return mConfig;
+        }
+
+        public SpiRecord getSpiRecord() {
+            return mSpi;
+        }
+
+        public EncapSocketRecord getSocketRecord() {
+            return mSocket;
+        }
+
+        /** always guarded by IpSecService#this */
+        @Override
+        public void freeUnderlyingResources() {
+            int spi = mSpi.getSpi();
+            try {
+                mSrvConfig
+                        .getNetdInstance()
+                        .ipSecDeleteSecurityAssociation(
+                                uid,
+                                mConfig.getSourceAddress(),
+                                mConfig.getDestinationAddress(),
+                                spi,
+                                mConfig.getMarkValue(),
+                                mConfig.getMarkMask(),
+                                mConfig.getXfrmInterfaceId());
+            } catch (RemoteException | ServiceSpecificException e) {
+                Log.e(TAG, "Failed to delete SA with ID: " + mResourceId, e);
+            }
+
+            getResourceTracker().give();
+        }
+
+        @Override
+        public void invalidate() throws RemoteException {
+            getUserRecord().removeTransformRecord(mResourceId);
+        }
+
+        @Override
+        protected ResourceTracker getResourceTracker() {
+            return getUserRecord().mTransformQuotaTracker;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder strBuilder = new StringBuilder();
+            strBuilder
+                    .append("{super=")
+                    .append(super.toString())
+                    .append(", mSocket=")
+                    .append(mSocket)
+                    .append(", mSpi.mResourceId=")
+                    .append(mSpi.mResourceId)
+                    .append(", mConfig=")
+                    .append(mConfig)
+                    .append("}");
+            return strBuilder.toString();
+        }
+    }
+
+    /**
+     * Tracks a single SA in the kernel, and manages cleanup paths. Once used in a Transform, the
+     * responsibility for cleaning up underlying resources will be passed to the TransformRecord
+     * object
+     */
+    private final class SpiRecord extends OwnedResourceRecord {
+        private final String mSourceAddress;
+        private final String mDestinationAddress;
+        private int mSpi;
+
+        private boolean mOwnedByTransform = false;
+
+        SpiRecord(int resourceId, String sourceAddress, String destinationAddress, int spi) {
+            super(resourceId);
+            mSourceAddress = sourceAddress;
+            mDestinationAddress = destinationAddress;
+            mSpi = spi;
+        }
+
+        /** always guarded by IpSecService#this */
+        @Override
+        public void freeUnderlyingResources() {
+            try {
+                if (!mOwnedByTransform) {
+                    mSrvConfig
+                            .getNetdInstance()
+                            .ipSecDeleteSecurityAssociation(
+                                    uid, mSourceAddress, mDestinationAddress, mSpi, 0 /* mark */,
+                                    0 /* mask */, 0 /* if_id */);
+                }
+            } catch (ServiceSpecificException | RemoteException e) {
+                Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId, e);
+            }
+
+            mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
+
+            getResourceTracker().give();
+        }
+
+        public int getSpi() {
+            return mSpi;
+        }
+
+        public String getDestinationAddress() {
+            return mDestinationAddress;
+        }
+
+        public void setOwnedByTransform() {
+            if (mOwnedByTransform) {
+                // Programming error
+                throw new IllegalStateException("Cannot own an SPI twice!");
+            }
+
+            mOwnedByTransform = true;
+        }
+
+        public boolean getOwnedByTransform() {
+            return mOwnedByTransform;
+        }
+
+        @Override
+        public void invalidate() throws RemoteException {
+            getUserRecord().removeSpiRecord(mResourceId);
+        }
+
+        @Override
+        protected ResourceTracker getResourceTracker() {
+            return getUserRecord().mSpiQuotaTracker;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder strBuilder = new StringBuilder();
+            strBuilder
+                    .append("{super=")
+                    .append(super.toString())
+                    .append(", mSpi=")
+                    .append(mSpi)
+                    .append(", mSourceAddress=")
+                    .append(mSourceAddress)
+                    .append(", mDestinationAddress=")
+                    .append(mDestinationAddress)
+                    .append(", mOwnedByTransform=")
+                    .append(mOwnedByTransform)
+                    .append("}");
+            return strBuilder.toString();
+        }
+    }
+
+    private final SparseBooleanArray mTunnelNetIds = new SparseBooleanArray();
+    final Range<Integer> mNetIdRange = ConnectivityManager.getIpSecNetIdRange();
+    private int mNextTunnelNetId = mNetIdRange.getLower();
+
+    /**
+     * Reserves a netId within the range of netIds allocated for IPsec tunnel interfaces
+     *
+     * <p>This method should only be called from Binder threads. Do not call this from within the
+     * system server as it will crash the system on failure.
+     *
+     * @return an integer key within the netId range, if successful
+     * @throws IllegalStateException if unsuccessful (all netId are currently reserved)
+     */
+    @VisibleForTesting
+    int reserveNetId() {
+        final int range = mNetIdRange.getUpper() - mNetIdRange.getLower() + 1;
+        synchronized (mTunnelNetIds) {
+            for (int i = 0; i < range; i++) {
+                final int netId = mNextTunnelNetId;
+                if (++mNextTunnelNetId > mNetIdRange.getUpper()) {
+                    mNextTunnelNetId = mNetIdRange.getLower();
+                }
+                if (!mTunnelNetIds.get(netId)) {
+                    mTunnelNetIds.put(netId, true);
+                    return netId;
+                }
+            }
+        }
+        throw new IllegalStateException("No free netIds to allocate");
+    }
+
+    @VisibleForTesting
+    void releaseNetId(int netId) {
+        synchronized (mTunnelNetIds) {
+            mTunnelNetIds.delete(netId);
+        }
+    }
+
+    /**
+     * Tracks an tunnel interface, and manages cleanup paths.
+     *
+     * <p>This class is not thread-safe, and expects that that users of this class will ensure
+     * synchronization and thread safety by holding the IpSecService.this instance lock
+     */
+    @VisibleForTesting
+    final class TunnelInterfaceRecord extends OwnedResourceRecord {
+        private final String mInterfaceName;
+
+        // outer addresses
+        private final String mLocalAddress;
+        private final String mRemoteAddress;
+
+        private final int mIkey;
+        private final int mOkey;
+
+        private final int mIfId;
+
+        private Network mUnderlyingNetwork;
+
+        TunnelInterfaceRecord(
+                int resourceId,
+                String interfaceName,
+                Network underlyingNetwork,
+                String localAddr,
+                String remoteAddr,
+                int ikey,
+                int okey,
+                int intfId) {
+            super(resourceId);
+
+            mInterfaceName = interfaceName;
+            mUnderlyingNetwork = underlyingNetwork;
+            mLocalAddress = localAddr;
+            mRemoteAddress = remoteAddr;
+            mIkey = ikey;
+            mOkey = okey;
+            mIfId = intfId;
+        }
+
+        /** always guarded by IpSecService#this */
+        @Override
+        public void freeUnderlyingResources() {
+            // Calls to netd
+            //       Teardown VTI
+            //       Delete global policies
+            try {
+                final INetd netd = mSrvConfig.getNetdInstance();
+                netd.ipSecRemoveTunnelInterface(mInterfaceName);
+
+                for (int selAddrFamily : ADDRESS_FAMILIES) {
+                    netd.ipSecDeleteSecurityPolicy(
+                            uid,
+                            selAddrFamily,
+                            IpSecManager.DIRECTION_OUT,
+                            mOkey,
+                            0xffffffff,
+                            mIfId);
+                    netd.ipSecDeleteSecurityPolicy(
+                            uid,
+                            selAddrFamily,
+                            IpSecManager.DIRECTION_IN,
+                            mIkey,
+                            0xffffffff,
+                            mIfId);
+                }
+            } catch (ServiceSpecificException | RemoteException e) {
+                Log.e(
+                        TAG,
+                        "Failed to delete VTI with interface name: "
+                                + mInterfaceName
+                                + " and id: "
+                                + mResourceId, e);
+            }
+
+            getResourceTracker().give();
+            releaseNetId(mIkey);
+            releaseNetId(mOkey);
+        }
+
+        @GuardedBy("IpSecService.this")
+        public void setUnderlyingNetwork(Network underlyingNetwork) {
+            // When #applyTunnelModeTransform is called, this new underlying network will be used to
+            // update the output mark of the input transform.
+            mUnderlyingNetwork = underlyingNetwork;
+        }
+
+        @GuardedBy("IpSecService.this")
+        public Network getUnderlyingNetwork() {
+            return mUnderlyingNetwork;
+        }
+
+        public String getInterfaceName() {
+            return mInterfaceName;
+        }
+
+        /** Returns the local, outer address for the tunnelInterface */
+        public String getLocalAddress() {
+            return mLocalAddress;
+        }
+
+        /** Returns the remote, outer address for the tunnelInterface */
+        public String getRemoteAddress() {
+            return mRemoteAddress;
+        }
+
+        public int getIkey() {
+            return mIkey;
+        }
+
+        public int getOkey() {
+            return mOkey;
+        }
+
+        public int getIfId() {
+            return mIfId;
+        }
+
+        @Override
+        protected ResourceTracker getResourceTracker() {
+            return getUserRecord().mTunnelQuotaTracker;
+        }
+
+        @Override
+        public void invalidate() {
+            getUserRecord().removeTunnelInterfaceRecord(mResourceId);
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder()
+                    .append("{super=")
+                    .append(super.toString())
+                    .append(", mInterfaceName=")
+                    .append(mInterfaceName)
+                    .append(", mUnderlyingNetwork=")
+                    .append(mUnderlyingNetwork)
+                    .append(", mLocalAddress=")
+                    .append(mLocalAddress)
+                    .append(", mRemoteAddress=")
+                    .append(mRemoteAddress)
+                    .append(", mIkey=")
+                    .append(mIkey)
+                    .append(", mOkey=")
+                    .append(mOkey)
+                    .append("}")
+                    .toString();
+        }
+    }
+
+    /**
+     * Tracks a UDP encap socket, and manages cleanup paths
+     *
+     * <p>While this class does not manage non-kernel resources, race conditions around socket
+     * binding require that the service creates the encap socket, binds it and applies the socket
+     * policy before handing it to a user.
+     */
+    private final class EncapSocketRecord extends OwnedResourceRecord {
+        private FileDescriptor mSocket;
+        private final int mPort;
+
+        EncapSocketRecord(int resourceId, FileDescriptor socket, int port) {
+            super(resourceId);
+            mSocket = socket;
+            mPort = port;
+        }
+
+        /** always guarded by IpSecService#this */
+        @Override
+        public void freeUnderlyingResources() {
+            Log.d(TAG, "Closing port " + mPort);
+            IoUtils.closeQuietly(mSocket);
+            mSocket = null;
+
+            getResourceTracker().give();
+        }
+
+        public int getPort() {
+            return mPort;
+        }
+
+        public FileDescriptor getFileDescriptor() {
+            return mSocket;
+        }
+
+        @Override
+        protected ResourceTracker getResourceTracker() {
+            return getUserRecord().mSocketQuotaTracker;
+        }
+
+        @Override
+        public void invalidate() {
+            getUserRecord().removeEncapSocketRecord(mResourceId);
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder()
+                    .append("{super=")
+                    .append(super.toString())
+                    .append(", mSocket=")
+                    .append(mSocket)
+                    .append(", mPort=")
+                    .append(mPort)
+                    .append("}")
+                    .toString();
+        }
+    }
+
+    /**
+     * Constructs a new IpSecService instance
+     *
+     * @param context Binder context for this service
+     */
+    private IpSecService(Context context) {
+        this(context, IpSecServiceConfiguration.GETSRVINSTANCE);
+    }
+
+    static IpSecService create(Context context)
+            throws InterruptedException {
+        final IpSecService service = new IpSecService(context);
+        service.connectNativeNetdService();
+        return service;
+    }
+
+    @NonNull
+    private AppOpsManager getAppOpsManager() {
+        AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        if(appOps == null) throw new RuntimeException("System Server couldn't get AppOps");
+        return appOps;
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public IpSecService(Context context, IpSecServiceConfiguration config) {
+        this(
+                context,
+                config,
+                (fd, uid) -> {
+                    try {
+                        TrafficStats.setThreadStatsUid(uid);
+                        TrafficStats.tagFileDescriptor(fd);
+                    } finally {
+                        TrafficStats.clearThreadStatsUid();
+                    }
+                });
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public IpSecService(Context context, IpSecServiceConfiguration config,
+            UidFdTagger uidFdTagger) {
+        mContext = context;
+        mSrvConfig = config;
+        mUidFdTagger = uidFdTagger;
+    }
+
+    public void systemReady() {
+        if (isNetdAlive()) {
+            Log.d(TAG, "IpSecService is ready");
+        } else {
+            Log.wtf(TAG, "IpSecService not ready: failed to connect to NetD Native Service!");
+        }
+    }
+
+    private void connectNativeNetdService() {
+        // Avoid blocking the system server to do this
+        new Thread() {
+            @Override
+            public void run() {
+                synchronized (IpSecService.this) {
+                    NetdService.get(NETD_FETCH_TIMEOUT_MS);
+                }
+            }
+        }.start();
+    }
+
+    synchronized boolean isNetdAlive() {
+        try {
+            final INetd netd = mSrvConfig.getNetdInstance();
+            if (netd == null) {
+                return false;
+            }
+            return netd.isAlive();
+        } catch (RemoteException re) {
+            return false;
+        }
+    }
+
+    /**
+     * Checks that the provided InetAddress is valid for use in an IPsec SA. The address must not be
+     * a wildcard address and must be in a numeric form such as 1.2.3.4 or 2001::1.
+     */
+    private static void checkInetAddress(String inetAddress) {
+        if (TextUtils.isEmpty(inetAddress)) {
+            throw new IllegalArgumentException("Unspecified address");
+        }
+
+        InetAddress checkAddr = InetAddresses.parseNumericAddress(inetAddress);
+
+        if (checkAddr.isAnyLocalAddress()) {
+            throw new IllegalArgumentException("Inappropriate wildcard address: " + inetAddress);
+        }
+    }
+
+    /**
+     * Checks the user-provided direction field and throws an IllegalArgumentException if it is not
+     * DIRECTION_IN or DIRECTION_OUT
+     */
+    private void checkDirection(int direction) {
+        switch (direction) {
+            case IpSecManager.DIRECTION_OUT:
+            case IpSecManager.DIRECTION_IN:
+                return;
+            case IpSecManager.DIRECTION_FWD:
+                // Only NETWORK_STACK or MAINLINE_NETWORK_STACK allowed to use forward policies
+                PermissionUtils.enforceNetworkStackPermission(mContext);
+                return;
+        }
+        throw new IllegalArgumentException("Invalid Direction: " + direction);
+    }
+
+    /** Get a new SPI and maintain the reservation in the system server */
+    @Override
+    public synchronized IpSecSpiResponse allocateSecurityParameterIndex(
+            String destinationAddress, int requestedSpi, IBinder binder) throws RemoteException {
+        checkInetAddress(destinationAddress);
+        // RFC 4303 Section 2.1 - 0=local, 1-255=reserved.
+        if (requestedSpi > 0 && requestedSpi < 256) {
+            throw new IllegalArgumentException("ESP SPI must not be in the range of 0-255.");
+        }
+        Objects.requireNonNull(binder, "Null Binder passed to allocateSecurityParameterIndex");
+
+        int callingUid = Binder.getCallingUid();
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
+        final int resourceId = mNextResourceId++;
+
+        int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
+        try {
+            if (!userRecord.mSpiQuotaTracker.isAvailable()) {
+                return new IpSecSpiResponse(
+                        IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
+            }
+
+            spi =
+                    mSrvConfig
+                            .getNetdInstance()
+                            .ipSecAllocateSpi(callingUid, "", destinationAddress, requestedSpi);
+            Log.d(TAG, "Allocated SPI " + spi);
+            userRecord.mSpiRecords.put(
+                    resourceId,
+                    new RefcountedResource<SpiRecord>(
+                            new SpiRecord(resourceId, "", destinationAddress, spi), binder));
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == OsConstants.ENOENT) {
+                return new IpSecSpiResponse(
+                        IpSecManager.Status.SPI_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
+            }
+            throw e;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, spi);
+    }
+
+    /* This method should only be called from Binder threads. Do not call this from
+     * within the system server as it will crash the system on failure.
+     */
+    private void releaseResource(RefcountedResourceArray resArray, int resourceId)
+            throws RemoteException {
+        resArray.getRefcountedResourceOrThrow(resourceId).userRelease();
+    }
+
+    /** Release a previously allocated SPI that has been registered with the system server */
+    @Override
+    public synchronized void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+        releaseResource(userRecord.mSpiRecords, resourceId);
+    }
+
+    /**
+     * This function finds and forcibly binds to a random system port, ensuring that the port cannot
+     * be unbound.
+     *
+     * <p>A socket cannot be un-bound from a port if it was bound to that port by number. To select
+     * a random open port and then bind by number, this function creates a temp socket, binds to a
+     * random port (specifying 0), gets that port number, and then uses is to bind the user's UDP
+     * Encapsulation Socket forcibly, so that it cannot be un-bound by the user with the returned
+     * FileHandle.
+     *
+     * <p>The loop in this function handles the inherent race window between un-binding to a port
+     * and re-binding, during which the system could *technically* hand that port out to someone
+     * else.
+     */
+    private int bindToRandomPort(FileDescriptor sockFd) throws IOException {
+        for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
+            try {
+                FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+                Os.bind(probeSocket, INADDR_ANY, 0);
+                int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
+                Os.close(probeSocket);
+                Log.v(TAG, "Binding to port " + port);
+                Os.bind(sockFd, INADDR_ANY, port);
+                return port;
+            } catch (ErrnoException e) {
+                // Someone miraculously claimed the port just after we closed probeSocket.
+                if (e.errno == OsConstants.EADDRINUSE) {
+                    continue;
+                }
+                throw e.rethrowAsIOException();
+            }
+        }
+        throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
+    }
+
+    /**
+     * Functional interface to do traffic tagging of given sockets to UIDs.
+     *
+     * <p>Specifically used by openUdpEncapsulationSocket to ensure data usage on the UDP encap
+     * sockets are billed to the UID that the UDP encap socket was created on behalf of.
+     *
+     * <p>Separate class so that the socket tagging logic can be mocked; TrafficStats uses static
+     * methods that cannot be easily mocked/tested.
+     */
+    @VisibleForTesting
+    public interface UidFdTagger {
+        /**
+         * Sets socket tag to assign all traffic to the provided UID.
+         *
+         * <p>Since the socket is created on behalf of an unprivileged application, all traffic
+         * should be accounted to the UID of the unprivileged application.
+         */
+        public void tag(FileDescriptor fd, int uid) throws IOException;
+    }
+
+    /**
+     * Open a socket via the system server and bind it to the specified port (random if port=0).
+     * This will return a PFD to the user that represent a bound UDP socket. The system server will
+     * cache the socket and a record of its owner so that it can and must be freed when no longer
+     * needed.
+     */
+    @Override
+    public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
+            throws RemoteException {
+        if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) {
+            throw new IllegalArgumentException(
+                    "Specified port number must be a valid non-reserved UDP port");
+        }
+        Objects.requireNonNull(binder, "Null Binder passed to openUdpEncapsulationSocket");
+
+        int callingUid = Binder.getCallingUid();
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
+        final int resourceId = mNextResourceId++;
+        FileDescriptor sockFd = null;
+        try {
+            if (!userRecord.mSocketQuotaTracker.isAvailable()) {
+                return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
+            }
+
+            sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+            mUidFdTagger.tag(sockFd, callingUid);
+
+            // This code is common to both the unspecified and specified port cases
+            Os.setsockoptInt(
+                    sockFd,
+                    OsConstants.IPPROTO_UDP,
+                    OsConstants.UDP_ENCAP,
+                    OsConstants.UDP_ENCAP_ESPINUDP);
+
+            mSrvConfig.getNetdInstance().ipSecSetEncapSocketOwner(
+                        new ParcelFileDescriptor(sockFd), callingUid);
+            if (port != 0) {
+                Log.v(TAG, "Binding to port " + port);
+                Os.bind(sockFd, INADDR_ANY, port);
+            } else {
+                port = bindToRandomPort(sockFd);
+            }
+
+            userRecord.mEncapSocketRecords.put(
+                    resourceId,
+                    new RefcountedResource<EncapSocketRecord>(
+                            new EncapSocketRecord(resourceId, sockFd, port), binder));
+            return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd);
+        } catch (IOException | ErrnoException e) {
+            IoUtils.closeQuietly(sockFd);
+        }
+        // If we make it to here, then something has gone wrong and we couldn't open a socket.
+        // The only reasonable condition that would cause that is resource unavailable.
+        return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
+    }
+
+    /** close a socket that has been been allocated by and registered with the system server */
+    @Override
+    public synchronized void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+        releaseResource(userRecord.mEncapSocketRecords, resourceId);
+    }
+
+    /**
+     * Create a tunnel interface for use in IPSec tunnel mode. The system server will cache the
+     * tunnel interface and a record of its owner so that it can and must be freed when no longer
+     * needed.
+     */
+    @Override
+    public synchronized IpSecTunnelInterfaceResponse createTunnelInterface(
+            String localAddr, String remoteAddr, Network underlyingNetwork, IBinder binder,
+            String callingPackage) {
+        enforceTunnelFeatureAndPermissions(callingPackage);
+        Objects.requireNonNull(binder, "Null Binder passed to createTunnelInterface");
+        Objects.requireNonNull(underlyingNetwork, "No underlying network was specified");
+        checkInetAddress(localAddr);
+        checkInetAddress(remoteAddr);
+
+        // TODO: Check that underlying network exists, and IP addresses not assigned to a different
+        //       network (b/72316676).
+
+        int callerUid = Binder.getCallingUid();
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(callerUid);
+        if (!userRecord.mTunnelQuotaTracker.isAvailable()) {
+            return new IpSecTunnelInterfaceResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
+        }
+
+        final int resourceId = mNextResourceId++;
+        final int ikey = reserveNetId();
+        final int okey = reserveNetId();
+        String intfName = String.format("%s%d", INetd.IPSEC_INTERFACE_PREFIX, resourceId);
+
+        try {
+            // Calls to netd:
+            //       Create VTI
+            //       Add inbound/outbound global policies
+            //              (use reqid = 0)
+            final INetd netd = mSrvConfig.getNetdInstance();
+            netd.ipSecAddTunnelInterface(intfName, localAddr, remoteAddr, ikey, okey, resourceId);
+
+            BinderUtils.withCleanCallingIdentity(() -> {
+                NetdUtils.setInterfaceUp(netd, intfName);
+            });
+
+            for (int selAddrFamily : ADDRESS_FAMILIES) {
+                // Always send down correct local/remote addresses for template.
+                netd.ipSecAddSecurityPolicy(
+                        callerUid,
+                        selAddrFamily,
+                        IpSecManager.DIRECTION_OUT,
+                        localAddr,
+                        remoteAddr,
+                        0,
+                        okey,
+                        0xffffffff,
+                        resourceId);
+                netd.ipSecAddSecurityPolicy(
+                        callerUid,
+                        selAddrFamily,
+                        IpSecManager.DIRECTION_IN,
+                        remoteAddr,
+                        localAddr,
+                        0,
+                        ikey,
+                        0xffffffff,
+                        resourceId);
+
+                // Add a forwarding policy on the tunnel interface. In order to support forwarding
+                // the IpSecTunnelInterface must have a forwarding policy matching the incoming SA.
+                //
+                // Unless a IpSecTransform is also applied against this interface in DIRECTION_FWD,
+                // forwarding will be blocked by default (as would be the case if this policy was
+                // absent).
+                //
+                // This is necessary only on the tunnel interface, and not any the interface to
+                // which traffic will be forwarded to.
+                netd.ipSecAddSecurityPolicy(
+                        callerUid,
+                        selAddrFamily,
+                        IpSecManager.DIRECTION_FWD,
+                        remoteAddr,
+                        localAddr,
+                        0,
+                        ikey,
+                        0xffffffff,
+                        resourceId);
+            }
+
+            userRecord.mTunnelInterfaceRecords.put(
+                    resourceId,
+                    new RefcountedResource<TunnelInterfaceRecord>(
+                            new TunnelInterfaceRecord(
+                                    resourceId,
+                                    intfName,
+                                    underlyingNetwork,
+                                    localAddr,
+                                    remoteAddr,
+                                    ikey,
+                                    okey,
+                                    resourceId),
+                            binder));
+            return new IpSecTunnelInterfaceResponse(IpSecManager.Status.OK, resourceId, intfName);
+        } catch (RemoteException e) {
+            // Release keys if we got an error.
+            releaseNetId(ikey);
+            releaseNetId(okey);
+            throw e.rethrowFromSystemServer();
+        } catch (Throwable t) {
+            // Release keys if we got an error.
+            releaseNetId(ikey);
+            releaseNetId(okey);
+            throw t;
+        }
+    }
+
+    /**
+     * Adds a new local address to the tunnel interface. This allows packets to be sent and received
+     * from multiple local IP addresses over the same tunnel.
+     */
+    @Override
+    public synchronized void addAddressToTunnelInterface(
+            int tunnelResourceId, LinkAddress localAddr, String callingPackage) {
+        enforceTunnelFeatureAndPermissions(callingPackage);
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+
+        // Get tunnelInterface record; if no such interface is found, will throw
+        // IllegalArgumentException
+        TunnelInterfaceRecord tunnelInterfaceInfo =
+                userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId);
+
+        try {
+            // We can assume general validity of the IP address, since we get them as a
+            // LinkAddress, which does some validation.
+            mSrvConfig
+                    .getNetdInstance()
+                    .interfaceAddAddress(
+                            tunnelInterfaceInfo.mInterfaceName,
+                            localAddr.getAddress().getHostAddress(),
+                            localAddr.getPrefixLength());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Remove a new local address from the tunnel interface. After removal, the address will no
+     * longer be available to send from, or receive on.
+     */
+    @Override
+    public synchronized void removeAddressFromTunnelInterface(
+            int tunnelResourceId, LinkAddress localAddr, String callingPackage) {
+        enforceTunnelFeatureAndPermissions(callingPackage);
+
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+        // Get tunnelInterface record; if no such interface is found, will throw
+        // IllegalArgumentException
+        TunnelInterfaceRecord tunnelInterfaceInfo =
+                userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId);
+
+        try {
+            // We can assume general validity of the IP address, since we get them as a
+            // LinkAddress, which does some validation.
+            mSrvConfig
+                    .getNetdInstance()
+                    .interfaceDelAddress(
+                            tunnelInterfaceInfo.mInterfaceName,
+                            localAddr.getAddress().getHostAddress(),
+                            localAddr.getPrefixLength());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Set TunnelInterface to use a specific underlying network. */
+    @Override
+    public synchronized void setNetworkForTunnelInterface(
+            int tunnelResourceId, Network underlyingNetwork, String callingPackage) {
+        enforceTunnelFeatureAndPermissions(callingPackage);
+        Objects.requireNonNull(underlyingNetwork, "No underlying network was specified");
+
+        final UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+
+        // Get tunnelInterface record; if no such interface is found, will throw
+        // IllegalArgumentException. userRecord.mTunnelInterfaceRecords is never null
+        final TunnelInterfaceRecord tunnelInterfaceInfo =
+                userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId);
+
+        final ConnectivityManager connectivityManager =
+                mContext.getSystemService(ConnectivityManager.class);
+        final LinkProperties lp = connectivityManager.getLinkProperties(underlyingNetwork);
+        if (tunnelInterfaceInfo.getInterfaceName().equals(lp.getInterfaceName())) {
+            throw new IllegalArgumentException(
+                    "Underlying network cannot be the network being exposed by this tunnel");
+        }
+
+        // It is meaningless to check if the network exists or is valid because the network might
+        // disconnect at any time after it passes the check.
+
+        tunnelInterfaceInfo.setUnderlyingNetwork(underlyingNetwork);
+    }
+
+    /**
+     * Delete a TunnelInterface that has been been allocated by and registered with the system
+     * server
+     */
+    @Override
+    public synchronized void deleteTunnelInterface(
+            int resourceId, String callingPackage) throws RemoteException {
+        enforceTunnelFeatureAndPermissions(callingPackage);
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+        releaseResource(userRecord.mTunnelInterfaceRecords, resourceId);
+    }
+
+    @VisibleForTesting
+    void validateAlgorithms(IpSecConfig config) throws IllegalArgumentException {
+        IpSecAlgorithm auth = config.getAuthentication();
+        IpSecAlgorithm crypt = config.getEncryption();
+        IpSecAlgorithm aead = config.getAuthenticatedEncryption();
+
+        // Validate the algorithm set
+        Preconditions.checkArgument(
+                aead != null || crypt != null || auth != null,
+                "No Encryption or Authentication algorithms specified");
+        Preconditions.checkArgument(
+                auth == null || auth.isAuthentication(),
+                "Unsupported algorithm for Authentication");
+        Preconditions.checkArgument(
+                crypt == null || crypt.isEncryption(), "Unsupported algorithm for Encryption");
+        Preconditions.checkArgument(
+                aead == null || aead.isAead(),
+                "Unsupported algorithm for Authenticated Encryption");
+        Preconditions.checkArgument(
+                aead == null || (auth == null && crypt == null),
+                "Authenticated Encryption is mutually exclusive with other Authentication "
+                        + "or Encryption algorithms");
+    }
+
+    private int getFamily(String inetAddress) {
+        int family = AF_UNSPEC;
+        InetAddress checkAddress = InetAddresses.parseNumericAddress(inetAddress);
+        if (checkAddress instanceof Inet4Address) {
+            family = AF_INET;
+        } else if (checkAddress instanceof Inet6Address) {
+            family = AF_INET6;
+        }
+        return family;
+    }
+
+    /**
+     * Checks an IpSecConfig parcel to ensure that the contents are valid and throws an
+     * IllegalArgumentException if they are not.
+     */
+    private void checkIpSecConfig(IpSecConfig config) {
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+
+        switch (config.getEncapType()) {
+            case IpSecTransform.ENCAP_NONE:
+                break;
+            case IpSecTransform.ENCAP_ESPINUDP:
+            case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
+                // Retrieve encap socket record; will throw IllegalArgumentException if not found
+                userRecord.mEncapSocketRecords.getResourceOrThrow(
+                        config.getEncapSocketResourceId());
+
+                int port = config.getEncapRemotePort();
+                if (port <= 0 || port > 0xFFFF) {
+                    throw new IllegalArgumentException("Invalid remote UDP port: " + port);
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid Encap Type: " + config.getEncapType());
+        }
+
+        validateAlgorithms(config);
+
+        // Retrieve SPI record; will throw IllegalArgumentException if not found
+        SpiRecord s = userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId());
+
+        // Check to ensure that SPI has not already been used.
+        if (s.getOwnedByTransform()) {
+            throw new IllegalStateException("SPI already in use; cannot be used in new Transforms");
+        }
+
+        // If no remote address is supplied, then use one from the SPI.
+        if (TextUtils.isEmpty(config.getDestinationAddress())) {
+            config.setDestinationAddress(s.getDestinationAddress());
+        }
+
+        // All remote addresses must match
+        if (!config.getDestinationAddress().equals(s.getDestinationAddress())) {
+            throw new IllegalArgumentException("Mismatched remote addresseses.");
+        }
+
+        // This check is technically redundant due to the chain of custody between the SPI and
+        // the IpSecConfig, but in the future if the dest is allowed to be set explicitly in
+        // the transform, this will prevent us from messing up.
+        checkInetAddress(config.getDestinationAddress());
+
+        // Require a valid source address for all transforms.
+        checkInetAddress(config.getSourceAddress());
+
+        // Check to ensure source and destination have the same address family.
+        String sourceAddress = config.getSourceAddress();
+        String destinationAddress = config.getDestinationAddress();
+        int sourceFamily = getFamily(sourceAddress);
+        int destinationFamily = getFamily(destinationAddress);
+        if (sourceFamily != destinationFamily) {
+            throw new IllegalArgumentException(
+                    "Source address ("
+                            + sourceAddress
+                            + ") and destination address ("
+                            + destinationAddress
+                            + ") have different address families.");
+        }
+
+        // Throw an error if UDP Encapsulation is not used in IPv4.
+        if (config.getEncapType() != IpSecTransform.ENCAP_NONE && sourceFamily != AF_INET) {
+            throw new IllegalArgumentException(
+                    "UDP Encapsulation is not supported for this address family");
+        }
+
+        switch (config.getMode()) {
+            case IpSecTransform.MODE_TRANSPORT:
+                break;
+            case IpSecTransform.MODE_TUNNEL:
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Invalid IpSecTransform.mode: " + config.getMode());
+        }
+
+        config.setMarkValue(0);
+        config.setMarkMask(0);
+    }
+
+    private static final String TUNNEL_OP = AppOpsManager.OPSTR_MANAGE_IPSEC_TUNNELS;
+
+    private void enforceTunnelFeatureAndPermissions(String callingPackage) {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)) {
+            throw new UnsupportedOperationException(
+                    "IPsec Tunnel Mode requires PackageManager.FEATURE_IPSEC_TUNNELS");
+        }
+
+        Objects.requireNonNull(callingPackage, "Null calling package cannot create IpSec tunnels");
+
+        // OP_MANAGE_IPSEC_TUNNELS will return MODE_ERRORED by default, including for the system
+        // server. If the appop is not granted, require that the caller has the MANAGE_IPSEC_TUNNELS
+        // permission or is the System Server.
+        if (AppOpsManager.MODE_ALLOWED == getAppOpsManager().noteOpNoThrow(
+                TUNNEL_OP, Binder.getCallingUid(), callingPackage)) {
+            return;
+        }
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "IpSecService");
+    }
+
+    private void createOrUpdateTransform(
+            IpSecConfig c, int resourceId, SpiRecord spiRecord, EncapSocketRecord socketRecord)
+            throws RemoteException {
+
+        int encapType = c.getEncapType(), encapLocalPort = 0, encapRemotePort = 0;
+        if (encapType != IpSecTransform.ENCAP_NONE) {
+            encapLocalPort = socketRecord.getPort();
+            encapRemotePort = c.getEncapRemotePort();
+        }
+
+        IpSecAlgorithm auth = c.getAuthentication();
+        IpSecAlgorithm crypt = c.getEncryption();
+        IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption();
+
+        String cryptName;
+        if (crypt == null) {
+            cryptName = (authCrypt == null) ? IpSecAlgorithm.CRYPT_NULL : "";
+        } else {
+            cryptName = crypt.getName();
+        }
+
+        mSrvConfig
+                .getNetdInstance()
+                .ipSecAddSecurityAssociation(
+                        Binder.getCallingUid(),
+                        c.getMode(),
+                        c.getSourceAddress(),
+                        c.getDestinationAddress(),
+                        (c.getNetwork() != null) ? c.getNetwork().getNetId() : 0,
+                        spiRecord.getSpi(),
+                        c.getMarkValue(),
+                        c.getMarkMask(),
+                        (auth != null) ? auth.getName() : "",
+                        (auth != null) ? auth.getKey() : new byte[] {},
+                        (auth != null) ? auth.getTruncationLengthBits() : 0,
+                        cryptName,
+                        (crypt != null) ? crypt.getKey() : new byte[] {},
+                        (crypt != null) ? crypt.getTruncationLengthBits() : 0,
+                        (authCrypt != null) ? authCrypt.getName() : "",
+                        (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
+                        (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
+                        encapType,
+                        encapLocalPort,
+                        encapRemotePort,
+                        c.getXfrmInterfaceId());
+    }
+
+    /**
+     * Create a IPsec transform, which represents a single security association in the kernel. The
+     * transform will be cached by the system server and must be freed when no longer needed. It is
+     * possible to free one, deleting the SA from underneath sockets that are using it, which will
+     * result in all of those sockets becoming unable to send or receive data.
+     */
+    @Override
+    public synchronized IpSecTransformResponse createTransform(
+            IpSecConfig c, IBinder binder, String callingPackage) throws RemoteException {
+        Objects.requireNonNull(c);
+        if (c.getMode() == IpSecTransform.MODE_TUNNEL) {
+            enforceTunnelFeatureAndPermissions(callingPackage);
+        }
+        checkIpSecConfig(c);
+        Objects.requireNonNull(binder, "Null Binder passed to createTransform");
+        final int resourceId = mNextResourceId++;
+
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+        List<RefcountedResource> dependencies = new ArrayList<>();
+
+        if (!userRecord.mTransformQuotaTracker.isAvailable()) {
+            return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
+        }
+
+        EncapSocketRecord socketRecord = null;
+        if (c.getEncapType() != IpSecTransform.ENCAP_NONE) {
+            RefcountedResource<EncapSocketRecord> refcountedSocketRecord =
+                    userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(
+                            c.getEncapSocketResourceId());
+            dependencies.add(refcountedSocketRecord);
+            socketRecord = refcountedSocketRecord.getResource();
+        }
+
+        RefcountedResource<SpiRecord> refcountedSpiRecord =
+                userRecord.mSpiRecords.getRefcountedResourceOrThrow(c.getSpiResourceId());
+        dependencies.add(refcountedSpiRecord);
+        SpiRecord spiRecord = refcountedSpiRecord.getResource();
+
+        createOrUpdateTransform(c, resourceId, spiRecord, socketRecord);
+
+        // SA was created successfully, time to construct a record and lock it away
+        userRecord.mTransformRecords.put(
+                resourceId,
+                new RefcountedResource<TransformRecord>(
+                        new TransformRecord(resourceId, c, spiRecord, socketRecord),
+                        binder,
+                        dependencies.toArray(new RefcountedResource[dependencies.size()])));
+        return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
+    }
+
+    /**
+     * Delete a transport mode transform that was previously allocated by + registered with the
+     * system server. If this is called on an inactive (or non-existent) transform, it will not
+     * return an error. It's safe to de-allocate transforms that may have already been deleted for
+     * other reasons.
+     */
+    @Override
+    public synchronized void deleteTransform(int resourceId) throws RemoteException {
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+        releaseResource(userRecord.mTransformRecords, resourceId);
+    }
+
+    /**
+     * Apply an active transport mode transform to a socket, which will apply the IPsec security
+     * association as a correspondent policy to the provided socket
+     */
+    @Override
+    public synchronized void applyTransportModeTransform(
+            ParcelFileDescriptor socket, int direction, int resourceId) throws RemoteException {
+        int callingUid = Binder.getCallingUid();
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
+        checkDirection(direction);
+        // Get transform record; if no transform is found, will throw IllegalArgumentException
+        TransformRecord info = userRecord.mTransformRecords.getResourceOrThrow(resourceId);
+
+        // TODO: make this a function.
+        if (info.pid != getCallingPid() || info.uid != callingUid) {
+            throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
+        }
+
+        // Get config and check that to-be-applied transform has the correct mode
+        IpSecConfig c = info.getConfig();
+        Preconditions.checkArgument(
+                c.getMode() == IpSecTransform.MODE_TRANSPORT,
+                "Transform mode was not Transport mode; cannot be applied to a socket");
+
+        mSrvConfig
+                .getNetdInstance()
+                .ipSecApplyTransportModeTransform(
+                        socket,
+                        callingUid,
+                        direction,
+                        c.getSourceAddress(),
+                        c.getDestinationAddress(),
+                        info.getSpiRecord().getSpi());
+    }
+
+    /**
+     * Remove transport mode transforms from a socket, applying the default (empty) policy. This
+     * ensures that NO IPsec policy is applied to the socket (would be the equivalent of applying a
+     * policy that performs no IPsec). Today the resourceId parameter is passed but not used:
+     * reserved for future improved input validation.
+     */
+    @Override
+    public synchronized void removeTransportModeTransforms(ParcelFileDescriptor socket)
+            throws RemoteException {
+        mSrvConfig
+                .getNetdInstance()
+                .ipSecRemoveTransportModeTransform(socket);
+    }
+
+    /**
+     * Apply an active tunnel mode transform to a TunnelInterface, which will apply the IPsec
+     * security association as a correspondent policy to the provided interface
+     */
+    @Override
+    public synchronized void applyTunnelModeTransform(
+            int tunnelResourceId, int direction,
+            int transformResourceId, String callingPackage) throws RemoteException {
+        enforceTunnelFeatureAndPermissions(callingPackage);
+        checkDirection(direction);
+
+        int callingUid = Binder.getCallingUid();
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
+
+        // Get transform record; if no transform is found, will throw IllegalArgumentException
+        TransformRecord transformInfo =
+                userRecord.mTransformRecords.getResourceOrThrow(transformResourceId);
+
+        // Get tunnelInterface record; if no such interface is found, will throw
+        // IllegalArgumentException
+        TunnelInterfaceRecord tunnelInterfaceInfo =
+                userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId);
+
+        // Get config and check that to-be-applied transform has the correct mode
+        IpSecConfig c = transformInfo.getConfig();
+        Preconditions.checkArgument(
+                c.getMode() == IpSecTransform.MODE_TUNNEL,
+                "Transform mode was not Tunnel mode; cannot be applied to a tunnel interface");
+
+        EncapSocketRecord socketRecord = null;
+        if (c.getEncapType() != IpSecTransform.ENCAP_NONE) {
+            socketRecord =
+                    userRecord.mEncapSocketRecords.getResourceOrThrow(c.getEncapSocketResourceId());
+        }
+        SpiRecord spiRecord = transformInfo.getSpiRecord();
+
+        int mark =
+                (direction == IpSecManager.DIRECTION_OUT)
+                        ? tunnelInterfaceInfo.getOkey()
+                        : tunnelInterfaceInfo.getIkey(); // Ikey also used for FWD policies
+
+        try {
+            // Default to using the invalid SPI of 0 for inbound SAs. This allows policies to skip
+            // SPI matching as part of the template resolution.
+            int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
+            c.setXfrmInterfaceId(tunnelInterfaceInfo.getIfId());
+
+            // TODO: enable this when UPDSA supports updating marks. Adding kernel support upstream
+            //     (and backporting) would allow us to narrow the mark space, and ensure that the SA
+            //     and SPs have matching marks (as VTI are meant to be built).
+            // Currently update does nothing with marks. Leave empty (defaulting to 0) to ensure the
+            //     config matches the actual allocated resources in the kernel.
+            // All SAs will have zero marks (from creation time), and any policy that matches the
+            //     same src/dst could match these SAs. Non-IpSecService governed processes that
+            //     establish floating policies with the same src/dst may result in undefined
+            //     behavior. This is generally limited to vendor code due to the permissions
+            //     (CAP_NET_ADMIN) required.
+            //
+            // c.setMarkValue(mark);
+            // c.setMarkMask(0xffffffff);
+
+            if (direction == IpSecManager.DIRECTION_OUT) {
+                // Set output mark via underlying network (output only)
+                c.setNetwork(tunnelInterfaceInfo.getUnderlyingNetwork());
+
+                // Set outbound SPI only. We want inbound to use any valid SA (old, new) on rekeys,
+                // but want to guarantee outbound packets are sent over the new SA.
+                spi = spiRecord.getSpi();
+            }
+
+            // Always update the policy with the relevant XFRM_IF_ID
+            for (int selAddrFamily : ADDRESS_FAMILIES) {
+                mSrvConfig
+                        .getNetdInstance()
+                        .ipSecUpdateSecurityPolicy(
+                                callingUid,
+                                selAddrFamily,
+                                direction,
+                                transformInfo.getConfig().getSourceAddress(),
+                                transformInfo.getConfig().getDestinationAddress(),
+                                spi, // If outbound, also add SPI to the policy.
+                                mark, // Must always set policy mark; ikey/okey for VTIs
+                                0xffffffff,
+                                c.getXfrmInterfaceId());
+            }
+
+            // Update SA with tunnel mark (ikey or okey based on direction)
+            createOrUpdateTransform(c, transformResourceId, spiRecord, socketRecord);
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode == EINVAL) {
+                throw new IllegalArgumentException(e.toString());
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mContext.enforceCallingOrSelfPermission(DUMP, TAG);
+
+        pw.println("IpSecService dump:");
+        pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
+        pw.println();
+
+        pw.println("mUserResourceTracker:");
+        pw.println(mUserResourceTracker);
+    }
+}