Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 93962f34 authored by Nathan Harold's avatar Nathan Harold
Browse files

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
parent 48b56655
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -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);
}
+2 −0
Original line number Diff line number Diff line
@@ -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:
+26 −44
Original line number Diff line number Diff line
@@ -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 =
+90 −22
Original line number Diff line number Diff line
@@ -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();
        }

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

    /**
     * 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;
                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");
+92 −51
Original line number Diff line number Diff line
@@ -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();
            //}

            if (transformId < 0) {
                throw new ErrnoException("addTransform", -transformId).rethrowAsIOException();
            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);

                /* 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,22 +168,28 @@ 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 {
        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) {
        //    transform.setTransformId(transformId);
        //    throw e.rethrowFromSystemServer();
        //} finally {
        mTransformId = INVALID_TRANSFORM_ID;
        //}
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        } finally {
            mResourceId = INVALID_RESOURCE_ID;
            mCloseGuard.close();
        }
    }

    @Override
    protected void finalize() throws Throwable {
@@ -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();
        }
Loading