Add Initial IPsec APIs to IpSecService
-Plumb IpSecManager APIs to NetD -Add Resource Management to IpSecService Bug: 33695893 Test: CTS verifies nearly all of these paths Change-Id: Ic43965c6158f28cac53810adbf5cf50d2c54f920
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<IpSecConfig> CREATOR =
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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");
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<SpiRecord> mSpiRecords = new SparseArray<>();
|
||||
|
||||
@GuardedBy("mTransformRecords")
|
||||
private final SparseArray<TransformRecord> 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);
|
||||
|
||||
Reference in New Issue
Block a user