From a09ac8b57ba3613916e3e09366ea11da9788621c Mon Sep 17 00:00:00 2001 From: Aaron Huang Date: Fri, 10 Dec 2021 17:36:49 +0800 Subject: 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 --- core/java/android/net/IIpSecService.aidl | 78 - core/java/android/net/IpSecAlgorithm.java | 500 ----- core/java/android/net/IpSecConfig.aidl | 20 - core/java/android/net/IpSecConfig.java | 358 ---- core/java/android/net/IpSecManager.java | 1034 ----------- core/java/android/net/IpSecSpiResponse.aidl | 20 - core/java/android/net/IpSecSpiResponse.java | 78 - core/java/android/net/IpSecTransform.java | 421 ----- core/java/android/net/IpSecTransformResponse.aidl | 20 - core/java/android/net/IpSecTransformResponse.java | 73 - .../android/net/IpSecTunnelInterfaceResponse.aidl | 20 - .../android/net/IpSecTunnelInterfaceResponse.java | 78 - core/java/android/net/IpSecUdpEncapResponse.aidl | 20 - core/java/android/net/IpSecUdpEncapResponse.java | 96 - packages/ConnectivityT/OWNERS | 3 +- packages/ConnectivityT/framework-t/Android.bp | 19 +- .../framework-t/src/android/net/IIpSecService.aidl | 78 + .../src/android/net/IpSecAlgorithm.java | 500 +++++ .../framework-t/src/android/net/IpSecConfig.aidl | 20 + .../framework-t/src/android/net/IpSecConfig.java | 358 ++++ .../framework-t/src/android/net/IpSecManager.java | 1034 +++++++++++ .../src/android/net/IpSecSpiResponse.aidl | 20 + .../src/android/net/IpSecSpiResponse.java | 78 + .../src/android/net/IpSecTransform.java | 421 +++++ .../src/android/net/IpSecTransformResponse.aidl | 20 + .../src/android/net/IpSecTransformResponse.java | 73 + .../android/net/IpSecTunnelInterfaceResponse.aidl | 20 + .../android/net/IpSecTunnelInterfaceResponse.java | 78 + .../src/android/net/IpSecUdpEncapResponse.aidl | 20 + .../src/android/net/IpSecUdpEncapResponse.java | 96 + packages/ConnectivityT/service/Android.bp | 20 +- .../src/com/android/server/IpSecService.java | 1917 ++++++++++++++++++++ .../core/java/com/android/server/IpSecService.java | 1917 -------------------- 33 files changed, 4767 insertions(+), 4741 deletions(-) delete mode 100644 core/java/android/net/IIpSecService.aidl delete mode 100644 core/java/android/net/IpSecAlgorithm.java delete mode 100644 core/java/android/net/IpSecConfig.aidl delete mode 100644 core/java/android/net/IpSecConfig.java delete mode 100644 core/java/android/net/IpSecManager.java delete mode 100644 core/java/android/net/IpSecSpiResponse.aidl delete mode 100644 core/java/android/net/IpSecSpiResponse.java delete mode 100644 core/java/android/net/IpSecTransform.java delete mode 100644 core/java/android/net/IpSecTransformResponse.aidl delete mode 100644 core/java/android/net/IpSecTransformResponse.java delete mode 100644 core/java/android/net/IpSecTunnelInterfaceResponse.aidl delete mode 100644 core/java/android/net/IpSecTunnelInterfaceResponse.java delete mode 100644 core/java/android/net/IpSecUdpEncapResponse.aidl delete mode 100644 core/java/android/net/IpSecUdpEncapResponse.java create mode 100644 packages/ConnectivityT/framework-t/src/android/net/IIpSecService.aidl create mode 100644 packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java create mode 100644 packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.aidl create mode 100644 packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java create mode 100644 packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java create mode 100644 packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.aidl create mode 100644 packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.java create mode 100644 packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java create mode 100644 packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.aidl create mode 100644 packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.java create mode 100644 packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl create mode 100644 packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java create mode 100644 packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.aidl create mode 100644 packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java create mode 100644 packages/ConnectivityT/service/src/com/android/server/IpSecService.java delete mode 100644 services/core/java/com/android/server/IpSecService.java diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl deleted file mode 100644 index 933256a3b475..000000000000 --- a/core/java/android/net/IIpSecService.aidl +++ /dev/null @@ -1,78 +0,0 @@ -/* -** 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/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java deleted file mode 100644 index 86052484eaf6..000000000000 --- a/core/java/android/net/IpSecAlgorithm.java +++ /dev/null @@ -1,500 +0,0 @@ -/* - * 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 RFC 4301, Security Architecture for the - * Internet Protocol - */ -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. - * - *

Valid lengths for this key are {128, 192, 256}. - */ - public static final String CRYPT_AES_CBC = "cbc(aes)"; - - /** - * AES-CTR Encryption/Ciphering Algorithm. - * - *

Valid lengths for keying material are {160, 224, 288}. - * - *

As per RFC3686 (Section - * 5.1), 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. - * - *

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. - * - *

@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. This algorithm is not recommended for use in - * new applications and is provided for legacy compatibility with 3gpp infrastructure. - * - *

Keys for this algorithm must be 128 bits in length. - * - *

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. This algorithm is not recommended for use in - * new applications and is provided for legacy compatibility with 3gpp infrastructure. - * - *

Keys for this algorithm must be 160 bits in length. - * - *

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. - * - *

Keys for this algorithm must be 256 bits in length. - * - *

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. - * - *

Keys for this algorithm must be 384 bits in length. - * - *

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. - * - *

Keys for this algorithm must be 512 bits in length. - * - *

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. - * - *

Keys for this algorithm must be 128 bits in length. - * - *

The only valid truncation length is 96 bits. - * - *

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. - * - *

@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. - * - *

Keys for this algorithm must be 128 bits in length. - * - *

The only valid truncation length is 96 bits. - * - *

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. - * - *

@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. - * - *

Valid lengths for keying material are {160, 224, 288}. - * - *

As per RFC4106 (Section - * 8.1), 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. - * - *

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. - * - *

Keys for this algorithm must be 288 bits in length. - * - *

As per RFC7634 (Section 2), - * keying material consists of a 256 bit key followed by a 32-bit salt. The salt is fixed per - * security association. - * - *

The only valid ICV (truncation) length is 128 bits. - * - *

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. - * - *

@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 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 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. - * - *

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. - * - *

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 CREATOR = - new Parcelable.Creator() { - 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. - * - *

Some algorithms may not be supported on old devices. Callers MUST check if an algorithm is - * supported before using it. - */ - @NonNull - public static Set getSupportedAlgorithms() { - return ENABLED_ALGOS; - } - - /** @hide */ - @VisibleForTesting - public static Set loadAlgos(Resources systemResources) { - final Set 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 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) : "") - .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/core/java/android/net/IpSecConfig.aidl b/core/java/android/net/IpSecConfig.aidl deleted file mode 100644 index eaefca74d3a0..000000000000 --- a/core/java/android/net/IpSecConfig.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java deleted file mode 100644 index 575c5ed968f8..000000000000 --- a/core/java/android/net/IpSecConfig.java +++ /dev/null @@ -1,358 +0,0 @@ -/* - * 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 - * - *

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 - * - *

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 CREATOR = - new Parcelable.Creator() { - 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/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java deleted file mode 100644 index c10680761ff1..000000000000 --- a/core/java/android/net/IpSecManager.java +++ /dev/null @@ -1,1034 +0,0 @@ -/* - * 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. - * - *

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 RFC 4301, Security Architecture for the - * Internet Protocol - */ -@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. - * - *

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. - * - *

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. - * - *

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. - * - *

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. - * - *

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. - * - *

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. - * - *

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. - * - *

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. - * - *

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. - * - *

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. - * - *

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. - * - *

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}. - * - *

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. - * - *

Rekey Procedure

- * - *

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. - * - *

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. - * - *

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}. - * - *

Rekey Procedure

- * - *

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. - * - *

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. - * - *

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}. - * - *

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. - * - *

Rekey Procedure

- * - *

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. - * - *

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. - * - *

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. - * - *

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. - * - *

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. - * - *

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. - * - *

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. - * - *

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. - * - *

{@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. - * - *

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. - * - *

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. - * - *

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. - * - *

See {@link UdpEncapsulationSocket} for the proper way to close the returned socket. - * - *

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 - * - *

IpSecTunnelInterface objects track tunnel interfaces that serve as - * local endpoints for IPsec tunnels. - * - *

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 - * - *

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 - * - *

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. - * - *

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. - * - *

To migrate IPsec tunnel mode traffic, a caller should: - * - *

    - *
  1. Update the IpSecTunnelInterface’s underlying network. - *
  2. Apply {@link IpSecTransform}(s) with matching addresses to this - * IpSecTunnelInterface. - *
- * - * @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 - * - *

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. - * - *

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). - *

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/core/java/android/net/IpSecSpiResponse.aidl b/core/java/android/net/IpSecSpiResponse.aidl deleted file mode 100644 index 6484a0013c53..000000000000 --- a/core/java/android/net/IpSecSpiResponse.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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/core/java/android/net/IpSecSpiResponse.java b/core/java/android/net/IpSecSpiResponse.java deleted file mode 100644 index f99e570fb761..000000000000 --- a/core/java/android/net/IpSecSpiResponse.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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 CREATOR = - new Parcelable.Creator() { - public IpSecSpiResponse createFromParcel(Parcel in) { - return new IpSecSpiResponse(in); - } - - public IpSecSpiResponse[] newArray(int size) { - return new IpSecSpiResponse[size]; - } - }; -} diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java deleted file mode 100644 index b48c1fdaf1b2..000000000000 --- a/core/java/android/net/IpSecTransform.java +++ /dev/null @@ -1,421 +0,0 @@ -/* - * 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. - * - *

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 RFC 4301, Security Architecture for the - * Internet Protocol - */ -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 - * RFC 3498. - * - * @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. - * - *

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). - * - *

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 - * - *

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. - * - *

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. - * - *

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. - * - *

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 - * RFC 4301). - * - *

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. - * - *

This allows IPsec traffic to pass through a NAT. - * - * @see RFC 3948, UDP Encapsulation of IPsec - * ESP Packets - * @see RFC 7296 section 2.23, - * NAT Traversal of IKEv2 - * @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}. - * - *

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/core/java/android/net/IpSecTransformResponse.aidl b/core/java/android/net/IpSecTransformResponse.aidl deleted file mode 100644 index 546230d5b888..000000000000 --- a/core/java/android/net/IpSecTransformResponse.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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/core/java/android/net/IpSecTransformResponse.java b/core/java/android/net/IpSecTransformResponse.java deleted file mode 100644 index a38488954fc0..000000000000 --- a/core/java/android/net/IpSecTransformResponse.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 CREATOR = - new Parcelable.Creator() { - public IpSecTransformResponse createFromParcel(Parcel in) { - return new IpSecTransformResponse(in); - } - - public IpSecTransformResponse[] newArray(int size) { - return new IpSecTransformResponse[size]; - } - }; -} diff --git a/core/java/android/net/IpSecTunnelInterfaceResponse.aidl b/core/java/android/net/IpSecTunnelInterfaceResponse.aidl deleted file mode 100644 index 7239221415ce..000000000000 --- a/core/java/android/net/IpSecTunnelInterfaceResponse.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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/core/java/android/net/IpSecTunnelInterfaceResponse.java b/core/java/android/net/IpSecTunnelInterfaceResponse.java deleted file mode 100644 index e3411e003d6b..000000000000 --- a/core/java/android/net/IpSecTunnelInterfaceResponse.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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 CREATOR = - new Parcelable.Creator() { - public IpSecTunnelInterfaceResponse createFromParcel(Parcel in) { - return new IpSecTunnelInterfaceResponse(in); - } - - public IpSecTunnelInterfaceResponse[] newArray(int size) { - return new IpSecTunnelInterfaceResponse[size]; - } - }; -} diff --git a/core/java/android/net/IpSecUdpEncapResponse.aidl b/core/java/android/net/IpSecUdpEncapResponse.aidl deleted file mode 100644 index 5e451f3651f1..000000000000 --- a/core/java/android/net/IpSecUdpEncapResponse.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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/core/java/android/net/IpSecUdpEncapResponse.java b/core/java/android/net/IpSecUdpEncapResponse.java deleted file mode 100644 index 4e7ba9b515d0..000000000000 --- a/core/java/android/net/IpSecUdpEncapResponse.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 CREATOR = - new Parcelable.Creator() { - public IpSecUdpEncapResponse createFromParcel(Parcel in) { - return new IpSecUdpEncapResponse(in); - } - - public IpSecUdpEncapResponse[] newArray(int size) { - return new IpSecUdpEncapResponse[size]; - } - }; -} diff --git a/packages/ConnectivityT/OWNERS b/packages/ConnectivityT/OWNERS index 4862377852ac..e267d19ad7e2 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 3e82b28ce67e..931a55b27ddb 100644 --- a/packages/ConnectivityT/framework-t/Android.bp +++ b/packages/ConnectivityT/framework-t/Android.bp @@ -100,6 +100,20 @@ filegroup { ], } +// 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 { 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 000000000000..933256a3b475 --- /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 000000000000..86052484eaf6 --- /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 RFC 4301, Security Architecture for the + * Internet Protocol + */ +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. + * + *

Valid lengths for this key are {128, 192, 256}. + */ + public static final String CRYPT_AES_CBC = "cbc(aes)"; + + /** + * AES-CTR Encryption/Ciphering Algorithm. + * + *

Valid lengths for keying material are {160, 224, 288}. + * + *

As per RFC3686 (Section + * 5.1), 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. + * + *

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. + * + *

@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. This algorithm is not recommended for use in + * new applications and is provided for legacy compatibility with 3gpp infrastructure. + * + *

Keys for this algorithm must be 128 bits in length. + * + *

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. This algorithm is not recommended for use in + * new applications and is provided for legacy compatibility with 3gpp infrastructure. + * + *

Keys for this algorithm must be 160 bits in length. + * + *

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. + * + *

Keys for this algorithm must be 256 bits in length. + * + *

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. + * + *

Keys for this algorithm must be 384 bits in length. + * + *

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. + * + *

Keys for this algorithm must be 512 bits in length. + * + *

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. + * + *

Keys for this algorithm must be 128 bits in length. + * + *

The only valid truncation length is 96 bits. + * + *

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. + * + *

@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. + * + *

Keys for this algorithm must be 128 bits in length. + * + *

The only valid truncation length is 96 bits. + * + *

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. + * + *

@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. + * + *

Valid lengths for keying material are {160, 224, 288}. + * + *

As per RFC4106 (Section + * 8.1), 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. + * + *

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. + * + *

Keys for this algorithm must be 288 bits in length. + * + *

As per RFC7634 (Section 2), + * keying material consists of a 256 bit key followed by a 32-bit salt. The salt is fixed per + * security association. + * + *

The only valid ICV (truncation) length is 128 bits. + * + *

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. + * + *

@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 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 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. + * + *

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. + * + *

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 CREATOR = + new Parcelable.Creator() { + 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. + * + *

Some algorithms may not be supported on old devices. Callers MUST check if an algorithm is + * supported before using it. + */ + @NonNull + public static Set getSupportedAlgorithms() { + return ENABLED_ALGOS; + } + + /** @hide */ + @VisibleForTesting + public static Set loadAlgos(Resources systemResources) { + final Set 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 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) : "") + .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 000000000000..eaefca74d3a0 --- /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 000000000000..575c5ed968f8 --- /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 + * + *

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 + * + *

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 CREATOR = + new Parcelable.Creator() { + 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 000000000000..c10680761ff1 --- /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. + * + *

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 RFC 4301, Security Architecture for the + * Internet Protocol + */ +@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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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}. + * + *

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. + * + *

Rekey Procedure

+ * + *

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. + * + *

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. + * + *

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}. + * + *

Rekey Procedure

+ * + *

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. + * + *

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. + * + *

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}. + * + *

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. + * + *

Rekey Procedure

+ * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

{@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. + * + *

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. + * + *

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. + * + *

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. + * + *

See {@link UdpEncapsulationSocket} for the proper way to close the returned socket. + * + *

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 + * + *

IpSecTunnelInterface objects track tunnel interfaces that serve as + * local endpoints for IPsec tunnels. + * + *

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 + * + *

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 + * + *

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. + * + *

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. + * + *

To migrate IPsec tunnel mode traffic, a caller should: + * + *

    + *
  1. Update the IpSecTunnelInterface’s underlying network. + *
  2. Apply {@link IpSecTransform}(s) with matching addresses to this + * IpSecTunnelInterface. + *
+ * + * @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 + * + *

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. + * + *

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). + *

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 000000000000..6484a0013c53 --- /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 000000000000..f99e570fb761 --- /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 CREATOR = + new Parcelable.Creator() { + 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 000000000000..b48c1fdaf1b2 --- /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. + * + *

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 RFC 4301, Security Architecture for the + * Internet Protocol + */ +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 + * RFC 3498. + * + * @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. + * + *

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). + * + *

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 + * + *

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. + * + *

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. + * + *

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. + * + *

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 + * RFC 4301). + * + *

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. + * + *

This allows IPsec traffic to pass through a NAT. + * + * @see RFC 3948, UDP Encapsulation of IPsec + * ESP Packets + * @see RFC 7296 section 2.23, + * NAT Traversal of IKEv2 + * @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}. + * + *

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 000000000000..546230d5b888 --- /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 000000000000..a38488954fc0 --- /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 CREATOR = + new Parcelable.Creator() { + 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 000000000000..7239221415ce --- /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 000000000000..e3411e003d6b --- /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 CREATOR = + new Parcelable.Creator() { + 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 000000000000..5e451f3651f1 --- /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 000000000000..4e7ba9b515d0 --- /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 CREATOR = + new Parcelable.Creator() { + 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 6a64910609ff..7b8817692b74 100644 --- a/packages/ConnectivityT/service/Android.bp +++ b/packages/ConnectivityT/service/Android.bp @@ -48,16 +48,28 @@ filegroup { ], } +// 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 000000000000..aeb814327e66 --- /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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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 implements IBinder.DeathRecipient { + private final T mResource; + private final List 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) + * + *

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. + * + *

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) + * + *

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 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 + * + *

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. + * + *

These are stored in separate arrays to improve debuggability and dump output clarity. + * + *

Resources are removed from this array when the user releases their explicit reference + * by calling one of the releaseResource() methods. + */ + final RefcountedResourceArray mSpiRecords = + new RefcountedResourceArray<>(SpiRecord.class.getSimpleName()); + final RefcountedResourceArray mTransformRecords = + new RefcountedResourceArray<>(TransformRecord.class.getSimpleName()); + final RefcountedResourceArray mEncapSocketRecords = + new RefcountedResourceArray<>(EncapSocketRecord.class.getSimpleName()); + final RefcountedResourceArray mTunnelInterfaceRecords = + new RefcountedResourceArray<>(TunnelInterfaceRecord.class.getSimpleName()); + + /** + * Trackers for quotas for each of the OwnedResource types. + * + *

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 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. + * + *

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. + * + *

RefcountedResourceArray prevents null insertions, and throws an IllegalArgumentException + * if a key is not found during a retrieval process. + */ + static class RefcountedResourceArray { + SparseArray> 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 getRefcountedResourceOrThrow(int key) { + RefcountedResource 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 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 mNetIdRange = ConnectivityManager.getIpSecNetIdRange(); + private int mNextTunnelNetId = mNetIdRange.getLower(); + + /** + * Reserves a netId within the range of netIds allocated for IPsec tunnel interfaces + * + *

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. + * + *

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 + * + *

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( + 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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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( + 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( + 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 dependencies = new ArrayList<>(); + + if (!userRecord.mTransformQuotaTracker.isAvailable()) { + return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); + } + + EncapSocketRecord socketRecord = null; + if (c.getEncapType() != IpSecTransform.ENCAP_NONE) { + RefcountedResource refcountedSocketRecord = + userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow( + c.getEncapSocketResourceId()); + dependencies.add(refcountedSocketRecord); + socketRecord = refcountedSocketRecord.getResource(); + } + + RefcountedResource 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( + 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); + } +} diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java deleted file mode 100644 index aeb814327e66..000000000000 --- a/services/core/java/com/android/server/IpSecService.java +++ /dev/null @@ -1,1917 +0,0 @@ -/* - * 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. - * - *

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. - * - *

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. - * - *

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. - * - *

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. - * - *

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. - * - *

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. - * - *

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 implements IBinder.DeathRecipient { - private final T mResource; - private final List 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) - * - *

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. - * - *

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) - * - *

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 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 - * - *

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. - * - *

These are stored in separate arrays to improve debuggability and dump output clarity. - * - *

Resources are removed from this array when the user releases their explicit reference - * by calling one of the releaseResource() methods. - */ - final RefcountedResourceArray mSpiRecords = - new RefcountedResourceArray<>(SpiRecord.class.getSimpleName()); - final RefcountedResourceArray mTransformRecords = - new RefcountedResourceArray<>(TransformRecord.class.getSimpleName()); - final RefcountedResourceArray mEncapSocketRecords = - new RefcountedResourceArray<>(EncapSocketRecord.class.getSimpleName()); - final RefcountedResourceArray mTunnelInterfaceRecords = - new RefcountedResourceArray<>(TunnelInterfaceRecord.class.getSimpleName()); - - /** - * Trackers for quotas for each of the OwnedResource types. - * - *

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 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. - * - *

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. - * - *

RefcountedResourceArray prevents null insertions, and throws an IllegalArgumentException - * if a key is not found during a retrieval process. - */ - static class RefcountedResourceArray { - SparseArray> 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 getRefcountedResourceOrThrow(int key) { - RefcountedResource 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 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 mNetIdRange = ConnectivityManager.getIpSecNetIdRange(); - private int mNextTunnelNetId = mNetIdRange.getLower(); - - /** - * Reserves a netId within the range of netIds allocated for IPsec tunnel interfaces - * - *

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. - * - *

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 - * - *

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( - 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. - * - *

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. - * - *

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. - * - *

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. - * - *

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. - * - *

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( - 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( - 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 dependencies = new ArrayList<>(); - - if (!userRecord.mTransformQuotaTracker.isAvailable()) { - return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); - } - - EncapSocketRecord socketRecord = null; - if (c.getEncapType() != IpSecTransform.ENCAP_NONE) { - RefcountedResource refcountedSocketRecord = - userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow( - c.getEncapSocketResourceId()); - dependencies.add(refcountedSocketRecord); - socketRecord = refcountedSocketRecord.getResource(); - } - - RefcountedResource 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( - 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); - } -} -- cgit v1.2.3-59-g8ed1b