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);
+ }
+}