From 8ea66052f2c2b31506884bd7c691d1ab90d3bacc Mon Sep 17 00:00:00 2001 From: Nathan Harold Date: Wed, 1 Mar 2017 18:55:06 -0800 Subject: [PATCH 1/4] Add a Skeleton IpSecService -Add IpSecService with the necessary glue to connect to netd -Add code to retrieve IpSecService from System Server Bug: 30984788 Test: b/34812052, b/34811227 Change-Id: I4cdcb643421141202f77a0e2f87a37012de0cd92 (cherry picked from commit 28084d89ec136b56f5012be33a0dea147962f9f6) --- core/java/android/net/IIpSecService.aidl | 24 ++++ core/java/android/net/IpSecManager.java | 31 +++-- .../java/com/android/server/IpSecService.java | 114 ++++++++++++++++++ 3 files changed, 152 insertions(+), 17 deletions(-) create mode 100644 core/java/android/net/IIpSecService.aidl create 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 new file mode 100644 index 0000000000..b8737fef72 --- /dev/null +++ b/core/java/android/net/IIpSecService.aidl @@ -0,0 +1,24 @@ +/* +** 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; + +/** + * @hide + */ +interface IIpSecService +{ +} diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 2c544e9b9b..93a76dfbb4 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -18,8 +18,6 @@ package android.net; import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.SystemApi; -import android.content.Context; -import android.os.INetworkManagementService; import android.os.ParcelFileDescriptor; import android.util.AndroidException; import dalvik.system.CloseGuard; @@ -79,11 +77,10 @@ public final class IpSecManager { } } - private final Context mContext; - private final INetworkManagementService mService; + private final IIpSecService mService; public static final class SecurityParameterIndex implements AutoCloseable { - private final Context mContext; + private final IIpSecService mService; private final InetAddress mDestinationAddress; private final CloseGuard mCloseGuard = CloseGuard.get(); private int mSpi; @@ -93,9 +90,10 @@ public final class IpSecManager { return mSpi; } - private SecurityParameterIndex(Context context, InetAddress destinationAddress, int spi) + private SecurityParameterIndex( + IIpSecService service, InetAddress destinationAddress, int spi) throws ResourceUnavailableException, SpiUnavailableException { - mContext = context; + mService = service; mDestinationAddress = destinationAddress; mSpi = spi; mCloseGuard.open("open"); @@ -152,7 +150,7 @@ public final class IpSecManager { public SecurityParameterIndex reserveSecurityParameterIndex( InetAddress destinationAddress, int requestedSpi) throws SpiUnavailableException, ResourceUnavailableException { - return new SecurityParameterIndex(mContext, destinationAddress, requestedSpi); + return new SecurityParameterIndex(mService, destinationAddress, requestedSpi); } /** @@ -260,19 +258,19 @@ public final class IpSecManager { */ public static final class UdpEncapsulationSocket implements AutoCloseable { private final FileDescriptor mFd; - private final Context mContext; + private final IIpSecService mService; private final CloseGuard mCloseGuard = CloseGuard.get(); - private UdpEncapsulationSocket(Context context, int port) + private UdpEncapsulationSocket(IIpSecService service, int port) throws ResourceUnavailableException { - mContext = context; + mService = service; mCloseGuard.open("constructor"); // TODO: go down to the kernel and get a socket on the specified mFd = new FileDescriptor(); } - private UdpEncapsulationSocket(Context context) throws ResourceUnavailableException { - mContext = context; + private UdpEncapsulationSocket(IIpSecService service) throws ResourceUnavailableException { + mService = service; mCloseGuard.open("constructor"); // TODO: go get a random socket on a random port mFd = new FileDescriptor(); @@ -339,7 +337,7 @@ public final class IpSecManager { public UdpEncapsulationSocket openUdpEncapsulationSocket(int port) throws IOException, ResourceUnavailableException { // Temporary code - return new UdpEncapsulationSocket(mContext, port); + return new UdpEncapsulationSocket(mService, port); } /** @@ -363,7 +361,7 @@ public final class IpSecManager { public UdpEncapsulationSocket openUdpEncapsulationSocket() throws IOException, ResourceUnavailableException { // Temporary code - return new UdpEncapsulationSocket(mContext); + return new UdpEncapsulationSocket(mService); } /** @@ -372,8 +370,7 @@ public final class IpSecManager { * @param context the application context for this manager * @hide */ - public IpSecManager(Context context, INetworkManagementService service) { - mContext = checkNotNull(context, "missing context"); + public IpSecManager(IIpSecService service) { mService = checkNotNull(service, "missing service"); } } diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java new file mode 100644 index 0000000000..994adc480f --- /dev/null +++ b/services/core/java/com/android/server/IpSecService.java @@ -0,0 +1,114 @@ +/* + * 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 android.content.Context; +import android.net.IIpSecService; +import android.net.INetd; +import android.net.util.NetdService; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** @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"; + + /** Binder context for this service */ + private final Context mContext; + + private Object mLock = new Object(); + + private static final int NETD_FETCH_TIMEOUT = 5000; //ms + + /** + * Constructs a new IpSecService instance + * + * @param context Binder context for this service + */ + private IpSecService(Context context) { + mContext = context; + } + + static IpSecService create(Context context) throws InterruptedException { + final IpSecService service = new IpSecService(context); + service.connectNativeNetdService(); + return service; + } + + public void systemReady() { + if (isNetdAlive()) { + Slog.d(TAG, "IpSecService is ready"); + } else { + Slog.wtf(TAG, "IpSecService not ready: failed to connect to NetD Native Service!"); + } + } + + private void connectNativeNetdService() { + // Avoid blocking the system server to do this + Thread t = + new Thread( + new Runnable() { + @Override + public void run() { + synchronized (mLock) { + NetdService.get(NETD_FETCH_TIMEOUT); + } + } + }); + t.run(); + } + + INetd getNetdInstance() { + final INetd netd = NetdService.getInstance(); + if (netd == null) { + throw new RemoteException("Failed to Get Netd Instance").rethrowFromSystemServer(); + } + return netd; + } + + boolean isNetdAlive() { + synchronized (mLock) { + final INetd netd = getNetdInstance(); + if (netd == null) { + return false; + } + + try { + return netd.isAlive(); + } catch (RemoteException re) { + return false; + } + } + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mContext.enforceCallingOrSelfPermission(DUMP, TAG); + + pw.println("IpSecService Log:"); + pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead")); + pw.println(); + } +} From 18fd6082866aaa960b6343637cc959449bbb19d5 Mon Sep 17 00:00:00 2001 From: Nathan Harold Date: Wed, 29 Mar 2017 10:47:59 -0700 Subject: [PATCH 2/4] Change reserveSecurityParameterIndex() to take a remoteAddress To make the SPI reservation more semantically consistent with the transform creation API, and to ensure that we always create SPI reservations relative to a well-known remote, we should take the SPI request relative to a remote (rather than to a destination). This necessitates that we now consider direction separately, which is used for keying the SA-Id. Bug: 36073210 Test: compilation Change-Id: I81e955c20128c1f8e04fd68eb26669561f827a78 (cherry picked from commit c4f879925b58b1b5ca9a3cfdc898c20cbf56355a) --- core/java/android/net/IpSecManager.java | 26 ++++++++++------------- core/java/android/net/IpSecTransform.java | 4 ++-- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 93a76dfbb4..83f4cc97b8 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -81,7 +81,7 @@ public final class IpSecManager { public static final class SecurityParameterIndex implements AutoCloseable { private final IIpSecService mService; - private final InetAddress mDestinationAddress; + private final InetAddress mRemoteAddress; private final CloseGuard mCloseGuard = CloseGuard.get(); private int mSpi; @@ -91,10 +91,10 @@ public final class IpSecManager { } private SecurityParameterIndex( - IIpSecService service, InetAddress destinationAddress, int spi) + IIpSecService service, int direction, InetAddress remoteAddress, int spi) throws ResourceUnavailableException, SpiUnavailableException { mService = service; - mDestinationAddress = destinationAddress; + mRemoteAddress = remoteAddress; mSpi = spi; mCloseGuard.open("open"); } @@ -102,13 +102,9 @@ public final class IpSecManager { /** * Release an SPI that was previously reserved. * - *

Release an SPI for use by other users in the system. This will fail if the SPI is - * currently in use by an IpSecTransform. - * - * @param destinationAddress SPIs must be unique for each combination of SPI and destination - * address. Thus, the destinationAddress to which the SPI will communicate must be - * supplied. - * @param spi the previously reserved SPI to be freed. + *

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() { @@ -134,13 +130,13 @@ public final class IpSecManager { public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; /** - * Reserve an SPI for traffic bound towards the specified destination address. + * Reserve an SPI for traffic bound towards the specified remote address. * *

If successful, this SPI is guaranteed available until released by a call to {@link * SecurityParameterIndex#close()}. * - * @param destinationAddress SPIs must be unique for each combination of SPI and destination - * address. + * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT} + * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress. * @param requestedSpi the requested SPI, or '0' to allocate a random SPI. * @return the reserved SecurityParameterIndex * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated @@ -148,9 +144,9 @@ public final class IpSecManager { * @throws SpiUnavailableException indicating that a particular SPI cannot be reserved */ public SecurityParameterIndex reserveSecurityParameterIndex( - InetAddress destinationAddress, int requestedSpi) + int direction, InetAddress remoteAddress, int requestedSpi) throws SpiUnavailableException, ResourceUnavailableException { - return new SecurityParameterIndex(mService, destinationAddress, requestedSpi); + return new SecurityParameterIndex(mService, direction, remoteAddress, requestedSpi); } /** diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java index d6dd28bec3..5c0bbe6a14 100644 --- a/core/java/android/net/IpSecTransform.java +++ b/core/java/android/net/IpSecTransform.java @@ -307,7 +307,7 @@ public final class IpSecTransform implements AutoCloseable { *

Care should be chosen when selecting an SPI to ensure that is is as unique as * possible. Random number generation is a reasonable approach to selecting an SPI. For * outbound SPIs, they must be reserved by calling {@link - * IpSecManager#reserveSecurityParameterIndex(InetAddress, int)}. Otherwise, Transforms will + * IpSecManager#reserveSecurityParameterIndex(int, InetAddress, int)}. Otherwise, Transforms will * fail to build. * *

Unless an SPI is set for a given direction, traffic in that direction will be @@ -329,7 +329,7 @@ public final class IpSecTransform implements AutoCloseable { *

Care should be chosen when selecting an SPI to ensure that is is as unique as * possible. Random number generation is a reasonable approach to selecting an SPI. For * outbound SPIs, they must be reserved by calling {@link - * IpSecManager#reserveSecurityParameterIndex(InetAddress, int)}. Otherwise, Transforms will + * IpSecManager#reserveSecurityParameterIndex(int, InetAddress, int)}. Otherwise, Transforms will * fail to activate. * *

Unless an SPI is set for a given direction, traffic in that direction will be From 8e518b425516b372ad9b2c1420035c5dbe53e389 Mon Sep 17 00:00:00 2001 From: Nathan Harold Date: Thu, 30 Mar 2017 11:01:37 -0700 Subject: [PATCH 3/4] IpSecManager and IpSecTransform API Cleanup -Remove Int-based SPI usage from the IpSecTransform.Builder This is essentially a less-safe method overload, and it is both unnecessary and difficult to implement: the cross-validation between SPI and Transform is actually useful, and the kernel requires two different mechanisms to use an unreserved vs a reserved (alloc'd) SPI: CREATESA vs UPDATESA, which makes this hard to support. API Council has questioned the value of this, and they are right: everything points to "remove this". In the future, if we find that SPI reservation is overhead, we can always add it back. -Hiding the TunnelMode builder method and application/remove methods. These will not land by the time the next API stabilizes, so better to hide them now that this is a near-certainty. Expectation is to un-hide them in the subsequent API bump. Bug: 36073210 Test: Compilation, verified nobody is calling these stubs Change-Id: Ic1a3f2cf7128633318ac175d6b56b45eb8d21cab (cherry picked from commit 48b566557d5a66d4476008b3c59b815eb78cb373) --- core/java/android/net/IpSecManager.java | 2 -- core/java/android/net/IpSecTransform.java | 30 +++-------------------- 2 files changed, 3 insertions(+), 29 deletions(-) diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 83f4cc97b8..3fcdb7e28c 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -197,7 +197,6 @@ public final class IpSecManager { * @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform. * @hide */ - @SystemApi public void applyTunnelModeTransform(Network net, IpSecTransform transform) {} /** @@ -242,7 +241,6 @@ public final class IpSecManager { * network * @hide */ - @SystemApi public void removeTunnelModeTransform(Network net, IpSecTransform transform) {} /** diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java index 5c0bbe6a14..74d60106c0 100644 --- a/core/java/android/net/IpSecTransform.java +++ b/core/java/android/net/IpSecTransform.java @@ -305,32 +305,9 @@ public final class IpSecTransform implements AutoCloseable { * given destination address. * *

Care should be chosen when selecting an SPI to ensure that is is as unique as - * possible. Random number generation is a reasonable approach to selecting an SPI. For - * outbound SPIs, they must be reserved by calling {@link - * IpSecManager#reserveSecurityParameterIndex(int, InetAddress, int)}. Otherwise, Transforms will - * fail to build. - * - *

Unless an SPI is set for a given direction, traffic in that direction will be - * sent/received without any IPsec applied. - * - * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT} - * @param spi a unique 32-bit integer to identify transformed traffic - */ - public IpSecTransform.Builder setSpi(@TransformDirection int direction, int spi) { - mConfig.flow[direction].spi = spi; - return this; - } - - /** - * Set the SPI, which uniquely identifies a particular IPsec session from others. Because - * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a - * given destination address. - * - *

Care should be chosen when selecting an SPI to ensure that is is as unique as - * possible. Random number generation is a reasonable approach to selecting an SPI. For - * outbound SPIs, they must be reserved by calling {@link - * IpSecManager#reserveSecurityParameterIndex(int, InetAddress, int)}. Otherwise, Transforms will - * fail to activate. + * possible. To reserve a value call {@link IpSecManager#reserveSecurityParameterIndex(int, + * InetAddress, int)}. Otherwise, SPI collisions would prevent a transform from being + * activated. IpSecManager#reserveSecurityParameterIndex(int, InetAddres$s, int)}. * *

Unless an SPI is set for a given direction, traffic in that direction will be * sent/received without any IPsec applied. @@ -447,7 +424,6 @@ public final class IpSecTransform implements AutoCloseable { * properties is invalid. * @hide */ - @SystemApi public IpSecTransform buildTunnelModeTransform( InetAddress localAddress, InetAddress remoteAddress) { //FIXME: argument validation here From 7c30d6b8f23bfeb9d90ae8414b52892570dbbf5c Mon Sep 17 00:00:00 2001 From: Nathan Harold Date: Tue, 7 Mar 2017 13:23:36 -0800 Subject: [PATCH 4/4] Add Initial IPsec APIs to IpSecService -Plumb IpSecManager APIs to NetD -Add Resource Management to IpSecService Bug: 30984788 Test: b/34812052, b/34811227 Change-Id: Ic43965c6158f28cac53810adbf5cf50d2c54f920 (cherry picked from commit 93962f34ce21f5aac825afbcebf2f3e8c7a30910) --- core/java/android/net/IIpSecService.aidl | 22 + core/java/android/net/IpSecAlgorithm.java | 2 + core/java/android/net/IpSecConfig.java | 70 ++-- core/java/android/net/IpSecManager.java | 112 ++++- core/java/android/net/IpSecTransform.java | 141 ++++--- .../java/com/android/server/IpSecService.java | 396 +++++++++++++++++- 6 files changed, 619 insertions(+), 124 deletions(-) diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl index b8737fef72..0aa3ce66eb 100644 --- a/core/java/android/net/IIpSecService.aidl +++ b/core/java/android/net/IIpSecService.aidl @@ -16,9 +16,31 @@ package android.net; +import android.net.Network; +import android.net.IpSecConfig; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; + /** * @hide */ interface IIpSecService { + Bundle reserveSecurityParameterIndex( + int direction, in String remoteAddress, int requestedSpi, in IBinder binder); + + void releaseSecurityParameterIndex(int resourceId); + + Bundle openUdpEncapsulationSocket(int port, in IBinder binder); + + void closeUdpEncapsulationSocket(in ParcelFileDescriptor socket); + + Bundle createTransportModeTransform(in IpSecConfig c, in IBinder binder); + + void deleteTransportModeTransform(int transformId); + + void applyTransportModeTransform(in ParcelFileDescriptor socket, int transformId); + + void removeTransportModeTransform(in ParcelFileDescriptor socket, int transformId); } diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java index da5cb37961..7fea4a25ca 100644 --- a/core/java/android/net/IpSecAlgorithm.java +++ b/core/java/android/net/IpSecAlgorithm.java @@ -164,6 +164,8 @@ public final class IpSecAlgorithm implements Parcelable { private static boolean isTruncationLengthValid(String algo, int truncLenBits) { switch (algo) { + case ALGO_CRYPT_AES_CBC: + return (truncLenBits == 128 || truncLenBits == 192 || truncLenBits == 256); case ALGO_AUTH_HMAC_MD5: return (truncLenBits >= 96 && truncLenBits <= 128); case ALGO_AUTH_HMAC_SHA1: diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java index b58bf421a8..13dc19f685 100644 --- a/core/java/android/net/IpSecConfig.java +++ b/core/java/android/net/IpSecConfig.java @@ -23,7 +23,7 @@ import java.net.UnknownHostException; /** @hide */ public final class IpSecConfig implements Parcelable { - private static final String TAG = IpSecConfig.class.getSimpleName(); + private static final String TAG = "IpSecConfig"; //MODE_TRANSPORT or MODE_TUNNEL int mode; @@ -43,13 +43,13 @@ public final class IpSecConfig implements Parcelable { int spi; // Encryption Algorithm - IpSecAlgorithm encryptionAlgo; + IpSecAlgorithm encryption; // Authentication Algorithm - IpSecAlgorithm authenticationAlgo; + IpSecAlgorithm authentication; } - Flow[] flow = new Flow[2]; + Flow[] flow = new Flow[] {new Flow(), new Flow()}; // For tunnel mode IPv4 UDP Encapsulation // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE @@ -57,17 +57,15 @@ public final class IpSecConfig implements Parcelable { int encapLocalPort; int encapRemotePort; - // An optional protocol to match with the selector - int selectorProto; - - // A bitmask of FEATURE_* indicating which of the fields - // of this class are valid. - long features; - // An interval, in seconds between the NattKeepalive packets int nattKeepaliveInterval; - public InetAddress getLocalIp() { + // Transport or Tunnel + public int getMode() { + return mode; + } + + public InetAddress getLocalAddress() { return localAddress; } @@ -75,19 +73,19 @@ public final class IpSecConfig implements Parcelable { return flow[direction].spi; } - public InetAddress getRemoteIp() { + public InetAddress getRemoteAddress() { return remoteAddress; } - public IpSecAlgorithm getEncryptionAlgo(int direction) { - return flow[direction].encryptionAlgo; + public IpSecAlgorithm getEncryption(int direction) { + return flow[direction].encryption; } - public IpSecAlgorithm getAuthenticationAlgo(int direction) { - return flow[direction].authenticationAlgo; + public IpSecAlgorithm getAuthentication(int direction) { + return flow[direction].authentication; } - Network getNetwork() { + public Network getNetwork() { return network; } @@ -103,18 +101,10 @@ public final class IpSecConfig implements Parcelable { return encapRemotePort; } - public int getSelectorProto() { - return selectorProto; - } - - int getNattKeepaliveInterval() { + public int getNattKeepaliveInterval() { return nattKeepaliveInterval; } - public boolean hasProperty(int featureBits) { - return (features & featureBits) == featureBits; - } - // Parcelable Methods @Override @@ -124,31 +114,25 @@ public final class IpSecConfig implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - out.writeLong(features); // TODO: Use a byte array or other better method for storing IPs that can also include scope out.writeString((localAddress != null) ? localAddress.getHostAddress() : null); // TODO: Use a byte array or other better method for storing IPs that can also include scope out.writeString((remoteAddress != null) ? remoteAddress.getHostAddress() : null); out.writeParcelable(network, flags); out.writeInt(flow[IpSecTransform.DIRECTION_IN].spi); - out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryptionAlgo, flags); - out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authenticationAlgo, flags); + out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryption, flags); + out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authentication, flags); out.writeInt(flow[IpSecTransform.DIRECTION_OUT].spi); - out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryptionAlgo, flags); - out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authenticationAlgo, flags); + out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryption, flags); + out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authentication, flags); out.writeInt(encapType); out.writeInt(encapLocalPort); out.writeInt(encapRemotePort); - out.writeInt(selectorProto); } // Package Private: Used by the IpSecTransform.Builder; // there should be no public constructor for this object - IpSecConfig() { - flow[IpSecTransform.DIRECTION_IN].spi = 0; - flow[IpSecTransform.DIRECTION_OUT].spi = 0; - nattKeepaliveInterval = 0; //FIXME constant - } + IpSecConfig() {} private static InetAddress readInetAddressFromParcel(Parcel in) { String addrString = in.readString(); @@ -164,24 +148,22 @@ public final class IpSecConfig implements Parcelable { } private IpSecConfig(Parcel in) { - features = in.readLong(); localAddress = readInetAddressFromParcel(in); remoteAddress = readInetAddressFromParcel(in); network = (Network) in.readParcelable(Network.class.getClassLoader()); flow[IpSecTransform.DIRECTION_IN].spi = in.readInt(); - flow[IpSecTransform.DIRECTION_IN].encryptionAlgo = + flow[IpSecTransform.DIRECTION_IN].encryption = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - flow[IpSecTransform.DIRECTION_IN].authenticationAlgo = + flow[IpSecTransform.DIRECTION_IN].authentication = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); flow[IpSecTransform.DIRECTION_OUT].spi = in.readInt(); - flow[IpSecTransform.DIRECTION_OUT].encryptionAlgo = + flow[IpSecTransform.DIRECTION_OUT].encryption = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - flow[IpSecTransform.DIRECTION_OUT].authenticationAlgo = + flow[IpSecTransform.DIRECTION_OUT].authentication = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); encapType = in.readInt(); encapLocalPort = in.readInt(); encapRemotePort = in.readInt(); - selectorProto = in.readInt(); } public static final Parcelable.Creator CREATOR = diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 3fcdb7e28c..6852beb065 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -17,8 +17,11 @@ package android.net; import static com.android.internal.util.Preconditions.checkNotNull; -import android.annotation.SystemApi; +import android.annotation.NonNull; +import android.os.Binder; +import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.os.RemoteException; import android.util.AndroidException; import dalvik.system.CloseGuard; import java.io.FileDescriptor; @@ -38,6 +41,29 @@ import java.net.Socket; public final class IpSecManager { private static final String TAG = "IpSecManager"; + /** + * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index. + * + *

No IPsec packet may contain an SPI of 0. + */ + 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 String KEY_STATUS = "status"; + /** @hide */ + public static final String KEY_RESOURCE_ID = "resourceId"; + /** @hide */ + public static final String KEY_SPI = "spi"; + /** @hide */ + public static final int INVALID_RESOURCE_ID = 0; + /** * Indicates that the combination of remote InetAddress and SPI was non-unique for a given * request. If encountered, selection of a new SPI is required before a transform may be @@ -83,22 +109,14 @@ public final class IpSecManager { private final IIpSecService mService; private final InetAddress mRemoteAddress; private final CloseGuard mCloseGuard = CloseGuard.get(); - private int mSpi; + private int mSpi = INVALID_SECURITY_PARAMETER_INDEX; + private int mResourceId; /** Return the underlying SPI held by this object */ public int getSpi() { return mSpi; } - private SecurityParameterIndex( - IIpSecService service, int direction, InetAddress remoteAddress, int spi) - throws ResourceUnavailableException, SpiUnavailableException { - mService = service; - mRemoteAddress = remoteAddress; - mSpi = spi; - mCloseGuard.open("open"); - } - /** * Release an SPI that was previously reserved. * @@ -108,7 +126,7 @@ public final class IpSecManager { */ @Override public void close() { - mSpi = INVALID_SECURITY_PARAMETER_INDEX; // TODO: Invalid SPI + mSpi = INVALID_SECURITY_PARAMETER_INDEX; mCloseGuard.close(); } @@ -120,14 +138,52 @@ public final class IpSecManager { close(); } - } - /** - * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index. - * - *

No IPsec packet may contain an SPI of 0. - */ - public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; + private SecurityParameterIndex( + @NonNull IIpSecService service, int direction, InetAddress remoteAddress, int spi) + throws ResourceUnavailableException, SpiUnavailableException { + mService = service; + mRemoteAddress = remoteAddress; + try { + Bundle result = + mService.reserveSecurityParameterIndex( + direction, remoteAddress.getHostAddress(), spi, new Binder()); + + if (result == null) { + throw new NullPointerException("Received null response from IpSecService"); + } + + int status = result.getInt(KEY_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.getInt(KEY_SPI); + mResourceId = result.getInt(KEY_RESOURCE_ID); + + 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"); + } + } /** * Reserve an SPI for traffic bound towards the specified remote address. @@ -184,7 +240,13 @@ public final class IpSecManager { } /* Call down to activate a transform */ - private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {} + private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { + try { + mService.applyTransportModeTransform(pfd, transform.getResourceId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } /** * Apply an active Tunnel Mode IPsec Transform to a network, which will tunnel all traffic to @@ -228,7 +290,13 @@ public final class IpSecManager { } /* Call down to activate a transform */ - private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {} + private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { + try { + mService.removeTransportModeTransform(pfd, transform.getResourceId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } /** * Remove a Tunnel Mode IPsec Transform from a {@link Network}. This must be used as part of @@ -255,7 +323,7 @@ public final class IpSecManager { private final IIpSecService mService; private final CloseGuard mCloseGuard = CloseGuard.get(); - private UdpEncapsulationSocket(IIpSecService service, int port) + private UdpEncapsulationSocket(@NonNull IIpSecService service, int port) throws ResourceUnavailableException { mService = service; mCloseGuard.open("constructor"); diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java index 74d60106c0..801e98c7b1 100644 --- a/core/java/android/net/IpSecTransform.java +++ b/core/java/android/net/IpSecTransform.java @@ -15,11 +15,21 @@ */ package android.net; +import static android.net.IpSecManager.INVALID_RESOURCE_ID; +import static android.net.IpSecManager.KEY_RESOURCE_ID; +import static android.net.IpSecManager.KEY_STATUS; + import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.SystemApi; import android.content.Context; -import android.system.ErrnoException; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; import android.util.Log; +import com.android.internal.util.Preconditions; import dalvik.system.CloseGuard; import java.io.IOException; import java.lang.annotation.Retention; @@ -86,39 +96,64 @@ public final class IpSecTransform implements AutoCloseable { @Retention(RetentionPolicy.SOURCE) public @interface EncapType {} - /** - * Sentinel for an invalid transform (means that this transform is inactive). - * - * @hide - */ - public static final int INVALID_TRANSFORM_ID = -1; - private IpSecTransform(Context context, IpSecConfig config) { mContext = context; mConfig = config; - mTransformId = INVALID_TRANSFORM_ID; + 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); + } + + private void checkResultStatusAndThrow(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 { - int transformId; synchronized (this) { - //try { - transformId = INVALID_TRANSFORM_ID; - //} catch (RemoteException e) { - // throw e.rethrowFromSystemServer(); - //} + try { + IIpSecService svc = getIpSecService(); + Bundle result = svc.createTransportModeTransform(mConfig, new Binder()); + int status = result.getInt(KEY_STATUS); + checkResultStatusAndThrow(status); + mResourceId = result.getInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID); - if (transformId < 0) { - throw new ErrnoException("addTransform", -transformId).rethrowAsIOException(); + /* Keepalive will silently fail if not needed by the config; but, if needed and + * it fails to start, we need to bail because a transform will not be reliable + * to use if keepalive is expected to offload and fails. + */ + // FIXME: if keepalive fails, we need to fail spectacularly + startKeepalive(mContext); + Log.d(TAG, "Added Transform with Id " + mResourceId); + mCloseGuard.open("build"); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); } - - startKeepalive(mContext); // Will silently fail if not required - mTransformId = transformId; - Log.d(TAG, "Added Transform with Id " + transformId); } - mCloseGuard.open("build"); return this; } @@ -133,21 +168,27 @@ public final class IpSecTransform implements AutoCloseable { * transform is no longer needed. */ public void close() { - Log.d(TAG, "Removing Transform with Id " + mTransformId); + Log.d(TAG, "Removing Transform with Id " + mResourceId); // Always safe to attempt cleanup - if (mTransformId == INVALID_TRANSFORM_ID) { + if (mResourceId == INVALID_RESOURCE_ID) { + mCloseGuard.close(); return; } - //try { - stopKeepalive(); - //} catch (RemoteException e) { - // transform.setTransformId(transformId); - // throw e.rethrowFromSystemServer(); - //} finally { - mTransformId = INVALID_TRANSFORM_ID; - //} - mCloseGuard.close(); + try { + /* Order matters here because the keepalive is best-effort but could fail in some + * horrible way to be removed if the wifi (or cell) subsystem has crashed, and we + * still want to clear out the transform. + */ + IIpSecService svc = getIpSecService(); + svc.deleteTransportModeTransform(mResourceId); + stopKeepalive(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } finally { + mResourceId = INVALID_RESOURCE_ID; + mCloseGuard.close(); + } } @Override @@ -164,7 +205,7 @@ public final class IpSecTransform implements AutoCloseable { } private final IpSecConfig mConfig; - private int mTransformId; + private int mResourceId; private final Context mContext; private final CloseGuard mCloseGuard = CloseGuard.get(); private ConnectivityManager.PacketKeepalive mKeepalive; @@ -200,6 +241,7 @@ public final class IpSecTransform implements AutoCloseable { /* Package */ void startKeepalive(Context c) { + // FIXME: NO_KEEPALIVE needs to be a constant if (mConfig.getNattKeepaliveInterval() == 0) { return; } @@ -208,7 +250,7 @@ public final class IpSecTransform implements AutoCloseable { (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE); if (mKeepalive != null) { - Log.e(TAG, "Keepalive already started for this IpSecTransform."); + Log.wtf(TAG, "Keepalive already started for this IpSecTransform."); return; } @@ -218,10 +260,11 @@ public final class IpSecTransform implements AutoCloseable { mConfig.getNetwork(), mConfig.getNattKeepaliveInterval(), mKeepaliveCallback, - mConfig.getLocalIp(), + mConfig.getLocalAddress(), mConfig.getEncapLocalPort(), - mConfig.getRemoteIp()); + mConfig.getRemoteAddress()); try { + // FIXME: this is still a horrible way to fudge the synchronous callback mKeepaliveSyncLock.wait(2000); } catch (InterruptedException e) { } @@ -231,6 +274,11 @@ public final class IpSecTransform implements AutoCloseable { } } + /* Package */ + int getResourceId() { + return mResourceId; + } + /* Package */ void stopKeepalive() { if (mKeepalive == null) { @@ -247,16 +295,6 @@ public final class IpSecTransform implements AutoCloseable { } } - /* Package */ - void setTransformId(int transformId) { - mTransformId = transformId; - } - - /* Package */ - int getTransformId() { - return mTransformId; - } - /** * Builder object to facilitate the creation of IpSecTransform objects. * @@ -280,7 +318,7 @@ public final class IpSecTransform implements AutoCloseable { */ public IpSecTransform.Builder setEncryption( @TransformDirection int direction, IpSecAlgorithm algo) { - mConfig.flow[direction].encryptionAlgo = algo; + mConfig.flow[direction].encryption = algo; return this; } @@ -295,7 +333,7 @@ public final class IpSecTransform implements AutoCloseable { */ public IpSecTransform.Builder setAuthentication( @TransformDirection int direction, IpSecAlgorithm algo) { - mConfig.flow[direction].authenticationAlgo = algo; + mConfig.flow[direction].authentication = algo; return this; } @@ -318,6 +356,8 @@ public final class IpSecTransform implements AutoCloseable { */ public IpSecTransform.Builder setSpi( @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) { + // TODO: convert to using the resource Id of the SPI. Then build() can validate + // the owner in the IpSecService mConfig.flow[direction].spi = spi.getSpi(); return this; } @@ -439,7 +479,8 @@ public final class IpSecTransform implements AutoCloseable { * * @param context current Context */ - public Builder(Context context) { + public Builder(@NonNull Context context) { + Preconditions.checkNotNull(context); mContext = context; mConfig = new IpSecConfig(); } diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index 994adc480f..a7ce95ba0c 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -17,23 +17,40 @@ package com.android.server; import static android.Manifest.permission.DUMP; +import static android.net.IpSecManager.INVALID_RESOURCE_ID; +import static android.net.IpSecManager.KEY_RESOURCE_ID; +import static android.net.IpSecManager.KEY_SPI; +import static android.net.IpSecManager.KEY_STATUS; import android.content.Context; import android.net.IIpSecService; import android.net.INetd; +import android.net.IpSecAlgorithm; +import android.net.IpSecConfig; +import android.net.IpSecManager; +import android.net.IpSecTransform; import android.net.util.NetdService; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.ServiceSpecificException; import android.util.Log; import android.util.Slog; - +import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.concurrent.atomic.AtomicInteger; /** @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[] DIRECTIONS = + new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN}; /** Binder context for this service */ private final Context mContext; @@ -42,6 +59,159 @@ public class IpSecService extends IIpSecService.Stub { private static final int NETD_FETCH_TIMEOUT = 5000; //ms + private AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0); + + private abstract class ManagedResource implements IBinder.DeathRecipient { + final int pid; + final int uid; + private IBinder mBinder; + + ManagedResource(IBinder binder) { + super(); + mBinder = binder; + pid = Binder.getCallingPid(); + uid = Binder.getCallingUid(); + + try { + mBinder.linkToDeath(this, 0); + } catch (RemoteException e) { + binderDied(); + } + } + + /** + * When this record is no longer needed for managing system resources this function should + * unlink all references held by the record to allow efficient garbage collection. + */ + public final void release() { + //Release all the underlying system resources first + releaseResources(); + + if (mBinder != null) { + mBinder.unlinkToDeath(this, 0); + } + mBinder = null; + + //remove this record so that it can be cleaned up + nullifyRecord(); + } + + /** + * If the Binder object dies, this function is called to free the system resources that are + * being managed by this record and to subsequently release this record for garbage + * collection + */ + public final void binderDied() { + release(); + } + + /** + * Implement this method to release all object references contained in the subclass to allow + * efficient garbage collection of the record. This should remove any references to the + * record from all other locations that hold a reference as the record is no longer valid. + */ + protected abstract void nullifyRecord(); + + /** + * Implement this method to release all system resources that are being protected by this + * record. Once the resources are released, the record should be invalidated and no longer + * used by calling releaseRecord() + */ + protected abstract void releaseResources(); + }; + + private final class TransformRecord extends ManagedResource { + private IpSecConfig mConfig; + private int mResourceId; + + TransformRecord(IpSecConfig config, int resourceId, IBinder binder) { + super(binder); + mConfig = config; + mResourceId = resourceId; + } + + public IpSecConfig getConfig() { + return mConfig; + } + + @Override + protected void releaseResources() { + for (int direction : DIRECTIONS) { + try { + getNetdInstance() + .ipSecDeleteSecurityAssociation( + mResourceId, + direction, + (mConfig.getLocalAddress() != null) + ? mConfig.getLocalAddress().getHostAddress() + : "", + (mConfig.getRemoteAddress() != null) + ? mConfig.getRemoteAddress().getHostAddress() + : "", + mConfig.getSpi(direction)); + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } catch (RemoteException e) { + Log.e(TAG, "Failed to delete SA with ID: " + mResourceId); + } + } + } + + @Override + protected void nullifyRecord() { + mConfig = null; + mResourceId = INVALID_RESOURCE_ID; + } + } + + private final class SpiRecord extends ManagedResource { + private final int mDirection; + private final String mLocalAddress; + private final String mRemoteAddress; + private final IBinder mBinder; + private int mSpi; + private int mResourceId; + + SpiRecord( + int resourceId, + int direction, + String localAddress, + String remoteAddress, + int spi, + IBinder binder) { + super(binder); + mResourceId = resourceId; + mDirection = direction; + mLocalAddress = localAddress; + mRemoteAddress = remoteAddress; + mSpi = spi; + mBinder = binder; + } + + protected void releaseResources() { + try { + getNetdInstance() + .ipSecDeleteSecurityAssociation( + mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi); + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } catch (RemoteException e) { + Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId); + } + } + + protected void nullifyRecord() { + mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX; + mResourceId = INVALID_RESOURCE_ID; + } + } + + @GuardedBy("mSpiRecords") + private final SparseArray mSpiRecords = new SparseArray<>(); + + @GuardedBy("mTransformRecords") + private final SparseArray mTransformRecords = new SparseArray<>(); + /** * Constructs a new IpSecService instance * @@ -80,22 +250,21 @@ public class IpSecService extends IIpSecService.Stub { t.run(); } - INetd getNetdInstance() { + INetd getNetdInstance() throws RemoteException { final INetd netd = NetdService.getInstance(); if (netd == null) { - throw new RemoteException("Failed to Get Netd Instance").rethrowFromSystemServer(); + throw new RemoteException("Failed to Get Netd Instance"); } return netd; } boolean isNetdAlive() { synchronized (mLock) { - final INetd netd = getNetdInstance(); - if (netd == null) { - return false; - } - try { + final INetd netd = getNetdInstance(); + if (netd == null) { + return false; + } return netd.isAlive(); } catch (RemoteException re) { return false; @@ -103,6 +272,217 @@ public class IpSecService extends IIpSecService.Stub { } } + @Override + /** Get a new SPI and maintain the reservation in the system server */ + public Bundle reserveSecurityParameterIndex( + int direction, String remoteAddress, int requestedSpi, IBinder binder) + throws RemoteException { + int resourceId = mNextResourceId.getAndIncrement(); + + int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX; + String localAddress = ""; + Bundle retBundle = new Bundle(3); + try { + spi = + getNetdInstance() + .ipSecAllocateSpi( + resourceId, + direction, + localAddress, + remoteAddress, + requestedSpi); + Log.d(TAG, "Allocated SPI " + spi); + retBundle.putInt(KEY_STATUS, IpSecManager.Status.OK); + retBundle.putInt(KEY_RESOURCE_ID, resourceId); + retBundle.putInt(KEY_SPI, spi); + synchronized (mSpiRecords) { + mSpiRecords.put( + resourceId, + new SpiRecord( + resourceId, direction, localAddress, remoteAddress, spi, binder)); + } + } catch (ServiceSpecificException e) { + // TODO: Add appropriate checks when other ServiceSpecificException types are supported + retBundle.putInt(KEY_STATUS, IpSecManager.Status.SPI_UNAVAILABLE); + retBundle.putInt(KEY_RESOURCE_ID, resourceId); + retBundle.putInt(KEY_SPI, spi); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return retBundle; + } + + /** Release a previously allocated SPI that has been registered with the system server */ + @Override + public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {} + + /** + * 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 Bundle openUdpEncapsulationSocket(int port, IBinder binder) throws RemoteException { + return null; + } + + /** close a socket that has been been allocated by and registered with the system server */ + @Override + public void closeUdpEncapsulationSocket(ParcelFileDescriptor socket) {} + + /** + * Create a transport mode transform, which represent two security associations (one in each + * direction) 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 Bundle createTransportModeTransform(IpSecConfig c, IBinder binder) + throws RemoteException { + // TODO: Basic input validation here since it's coming over the Binder + int resourceId = mNextResourceId.getAndIncrement(); + for (int direction : DIRECTIONS) { + IpSecAlgorithm auth = c.getAuthentication(direction); + IpSecAlgorithm crypt = c.getEncryption(direction); + try { + int result = + getNetdInstance() + .ipSecAddSecurityAssociation( + resourceId, + c.getMode(), + direction, + (c.getLocalAddress() != null) + ? c.getLocalAddress().getHostAddress() + : "", + (c.getRemoteAddress() != null) + ? c.getRemoteAddress().getHostAddress() + : "", + (c.getNetwork() != null) + ? c.getNetwork().getNetworkHandle() + : 0, + c.getSpi(direction), + (auth != null) ? auth.getName() : "", + (auth != null) ? auth.getKey() : null, + (auth != null) ? auth.getTruncationLengthBits() : 0, + (crypt != null) ? crypt.getName() : "", + (crypt != null) ? crypt.getKey() : null, + (crypt != null) ? crypt.getTruncationLengthBits() : 0, + c.getEncapType(), + c.getEncapLocalPort(), + c.getEncapRemotePort()); + if (result != c.getSpi(direction)) { + // TODO: cleanup the first SA if creation of second SA fails + Bundle retBundle = new Bundle(2); + retBundle.putInt(KEY_STATUS, IpSecManager.Status.SPI_UNAVAILABLE); + retBundle.putInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID); + return retBundle; + } + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } + } + synchronized (mTransformRecords) { + mTransformRecords.put(resourceId, new TransformRecord(c, resourceId, binder)); + } + + Bundle retBundle = new Bundle(2); + retBundle.putInt(KEY_STATUS, IpSecManager.Status.OK); + retBundle.putInt(KEY_RESOURCE_ID, resourceId); + return retBundle; + } + + /** + * 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 void deleteTransportModeTransform(int resourceId) throws RemoteException { + synchronized (mTransformRecords) { + TransformRecord record; + // We want to non-destructively get so that we can check credentials before removing + // this from the records. + record = mTransformRecords.get(resourceId); + + if (record == null) { + throw new IllegalArgumentException( + "Transform " + resourceId + " is not available to be deleted"); + } + + if (record.pid != Binder.getCallingPid() || record.uid != Binder.getCallingUid()) { + throw new SecurityException("Only the owner of an IpSec Transform may delete it!"); + } + + // TODO: if releaseResources() throws RemoteException, we can try again to clean up on + // binder death. Need to make sure that path is actually functional. + record.releaseResources(); + mTransformRecords.remove(resourceId); + record.nullifyRecord(); + } + } + + /** + * 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 void applyTransportModeTransform(ParcelFileDescriptor socket, int resourceId) + throws RemoteException { + + synchronized (mTransformRecords) { + TransformRecord info; + // FIXME: this code should be factored out into a security check + getter + info = mTransformRecords.get(resourceId); + + if (info == null) { + throw new IllegalArgumentException("Transform " + resourceId + " is not active"); + } + + // TODO: make this a function. + if (info.pid != getCallingPid() || info.uid != getCallingUid()) { + throw new SecurityException("Only the owner of an IpSec Transform may apply it!"); + } + + IpSecConfig c = info.getConfig(); + try { + for (int direction : DIRECTIONS) { + getNetdInstance() + .ipSecApplyTransportModeTransform( + socket.getFileDescriptor(), + resourceId, + direction, + (c.getLocalAddress() != null) + ? c.getLocalAddress().getHostAddress() + : "", + (c.getRemoteAddress() != null) + ? c.getRemoteAddress().getHostAddress() + : "", + c.getSpi(direction)); + } + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } + } + } + /** + * Remove a transport mode transform from a socket, applying the default (empty) policy. This + * will ensure 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 void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId) + throws RemoteException { + try { + getNetdInstance().ipSecRemoveTransportModeTransform(socket.getFileDescriptor()); + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { mContext.enforceCallingOrSelfPermission(DUMP, TAG);