Home | History | Annotate | Download | only in server
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server;
     18 
     19 import static android.Manifest.permission.DUMP;
     20 import static android.net.IpSecManager.INVALID_RESOURCE_ID;
     21 import static android.system.OsConstants.AF_INET;
     22 import static android.system.OsConstants.IPPROTO_UDP;
     23 import static android.system.OsConstants.SOCK_DGRAM;
     24 import static com.android.internal.util.Preconditions.checkNotNull;
     25 
     26 import android.content.Context;
     27 import android.net.IIpSecService;
     28 import android.net.INetd;
     29 import android.net.IpSecAlgorithm;
     30 import android.net.IpSecConfig;
     31 import android.net.IpSecManager;
     32 import android.net.IpSecSpiResponse;
     33 import android.net.IpSecTransform;
     34 import android.net.IpSecTransformResponse;
     35 import android.net.IpSecUdpEncapResponse;
     36 import android.net.util.NetdService;
     37 import android.os.Binder;
     38 import android.os.IBinder;
     39 import android.os.ParcelFileDescriptor;
     40 import android.os.RemoteException;
     41 import android.os.ServiceSpecificException;
     42 import android.system.ErrnoException;
     43 import android.system.Os;
     44 import android.system.OsConstants;
     45 import android.util.Log;
     46 import android.util.Slog;
     47 import android.util.SparseArray;
     48 import com.android.internal.annotations.GuardedBy;
     49 import java.io.FileDescriptor;
     50 import java.io.IOException;
     51 import java.io.PrintWriter;
     52 import java.net.InetAddress;
     53 import java.net.InetSocketAddress;
     54 import java.net.UnknownHostException;
     55 import java.util.concurrent.atomic.AtomicInteger;
     56 import libcore.io.IoUtils;
     57 
     58 /** @hide */
     59 public class IpSecService extends IIpSecService.Stub {
     60     private static final String TAG = "IpSecService";
     61     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
     62 
     63     private static final String NETD_SERVICE_NAME = "netd";
     64     private static final int[] DIRECTIONS =
     65             new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN};
     66 
     67     private static final int NETD_FETCH_TIMEOUT = 5000; //ms
     68     private static final int MAX_PORT_BIND_ATTEMPTS = 10;
     69     private static final InetAddress INADDR_ANY;
     70 
     71     static {
     72         try {
     73             INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
     74         } catch (UnknownHostException e) {
     75             throw new RuntimeException(e);
     76         }
     77     }
     78 
     79     static final int FREE_PORT_MIN = 1024; // ports 1-1023 are reserved
     80     static final int PORT_MAX = 0xFFFF; // ports are an unsigned 16-bit integer
     81 
     82     /* Binder context for this service */
     83     private final Context mContext;
     84 
     85     /** Should be a never-repeating global ID for resources */
     86     private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
     87 
     88     @GuardedBy("this")
     89     private final ManagedResourceArray<SpiRecord> mSpiRecords = new ManagedResourceArray<>();
     90 
     91     @GuardedBy("this")
     92     private final ManagedResourceArray<TransformRecord> mTransformRecords =
     93             new ManagedResourceArray<>();
     94 
     95     @GuardedBy("this")
     96     private final ManagedResourceArray<UdpSocketRecord> mUdpSocketRecords =
     97             new ManagedResourceArray<>();
     98 
     99     /**
    100      * The ManagedResource class provides a facility to cleanly and reliably release system
    101      * resources. It relies on two things: an IBinder that allows ManagedResource to automatically
    102      * clean up in the event that the Binder dies and a user-provided resourceId that should
    103      * uniquely identify the managed resource. To use this class, the user should implement the
    104      * releaseResources() method that is responsible for releasing system resources when invoked.
    105      */
    106     private abstract class ManagedResource implements IBinder.DeathRecipient {
    107         final int pid;
    108         final int uid;
    109         private IBinder mBinder;
    110         protected int mResourceId;
    111 
    112         private AtomicInteger mReferenceCount = new AtomicInteger(0);
    113 
    114         ManagedResource(int resourceId, IBinder binder) {
    115             super();
    116             mBinder = binder;
    117             mResourceId = resourceId;
    118             pid = Binder.getCallingPid();
    119             uid = Binder.getCallingUid();
    120 
    121             try {
    122                 mBinder.linkToDeath(this, 0);
    123             } catch (RemoteException e) {
    124                 binderDied();
    125             }
    126         }
    127 
    128         public void addReference() {
    129             mReferenceCount.incrementAndGet();
    130         }
    131 
    132         public void removeReference() {
    133             if (mReferenceCount.decrementAndGet() < 0) {
    134                 Log.wtf(TAG, "Programming error: negative reference count");
    135             }
    136         }
    137 
    138         public boolean isReferenced() {
    139             return (mReferenceCount.get() > 0);
    140         }
    141 
    142         public void checkOwnerOrSystemAndThrow() {
    143             if (uid != Binder.getCallingUid()
    144                     && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
    145                 throw new SecurityException("Only the owner may access managed resources!");
    146             }
    147         }
    148 
    149         /**
    150          * When this record is no longer needed for managing system resources this function should
    151          * clean up all system resources and nullify the record. This function shall perform all
    152          * necessary cleanup of the resources managed by this record.
    153          *
    154          * <p>NOTE: this function verifies ownership before allowing resources to be freed.
    155          */
    156         public final void release() throws RemoteException {
    157             synchronized (IpSecService.this) {
    158                 if (isReferenced()) {
    159                     throw new IllegalStateException(
    160                             "Cannot release a resource that has active references!");
    161                 }
    162 
    163                 if (mResourceId == INVALID_RESOURCE_ID) {
    164                     return;
    165                 }
    166 
    167                 releaseResources();
    168                 if (mBinder != null) {
    169                     mBinder.unlinkToDeath(this, 0);
    170                 }
    171                 mBinder = null;
    172 
    173                 mResourceId = INVALID_RESOURCE_ID;
    174             }
    175         }
    176 
    177         /**
    178          * If the Binder object dies, this function is called to free the system resources that are
    179          * being managed by this record and to subsequently release this record for garbage
    180          * collection
    181          */
    182         public final void binderDied() {
    183             try {
    184                 release();
    185             } catch (Exception e) {
    186                 Log.e(TAG, "Failed to release resource: " + e);
    187             }
    188         }
    189 
    190         /**
    191          * Implement this method to release all system resources that are being protected by this
    192          * record. Once the resources are released, the record should be invalidated and no longer
    193          * used by calling release(). This should NEVER be called directly.
    194          *
    195          * <p>Calls to this are always guarded by IpSecService#this
    196          */
    197         protected abstract void releaseResources() throws RemoteException;
    198     };
    199 
    200     /**
    201      * Minimal wrapper around SparseArray that performs ownership
    202      * validation on element accesses.
    203      */
    204     private class ManagedResourceArray<T extends ManagedResource> {
    205         SparseArray<T> mArray = new SparseArray<>();
    206 
    207         T get(int key) {
    208             T val = mArray.get(key);
    209             // The value should never be null unless the resource doesn't exist
    210             // (since we do not allow null resources to be added).
    211             if (val != null) {
    212                 val.checkOwnerOrSystemAndThrow();
    213             }
    214             return val;
    215         }
    216 
    217         void put(int key, T obj) {
    218             checkNotNull(obj, "Null resources cannot be added");
    219             mArray.put(key, obj);
    220         }
    221 
    222         void remove(int key) {
    223             mArray.remove(key);
    224         }
    225     }
    226 
    227     private final class TransformRecord extends ManagedResource {
    228         private final IpSecConfig mConfig;
    229         private final SpiRecord[] mSpis;
    230         private final UdpSocketRecord mSocket;
    231 
    232         TransformRecord(
    233                 int resourceId,
    234                 IBinder binder,
    235                 IpSecConfig config,
    236                 SpiRecord[] spis,
    237                 UdpSocketRecord socket) {
    238             super(resourceId, binder);
    239             mConfig = config;
    240             mSpis = spis;
    241             mSocket = socket;
    242 
    243             for (int direction : DIRECTIONS) {
    244                 mSpis[direction].addReference();
    245                 mSpis[direction].setOwnedByTransform();
    246             }
    247 
    248             if (mSocket != null) {
    249                 mSocket.addReference();
    250             }
    251         }
    252 
    253         public IpSecConfig getConfig() {
    254             return mConfig;
    255         }
    256 
    257         public SpiRecord getSpiRecord(int direction) {
    258             return mSpis[direction];
    259         }
    260 
    261         /** always guarded by IpSecService#this */
    262         @Override
    263         protected void releaseResources() {
    264             for (int direction : DIRECTIONS) {
    265                 int spi = mSpis[direction].getSpi();
    266                 try {
    267                     getNetdInstance()
    268                             .ipSecDeleteSecurityAssociation(
    269                                     mResourceId,
    270                                     direction,
    271                                     (mConfig.getLocalAddress() != null)
    272                                             ? mConfig.getLocalAddress().getHostAddress()
    273                                             : "",
    274                                     (mConfig.getRemoteAddress() != null)
    275                                             ? mConfig.getRemoteAddress().getHostAddress()
    276                                             : "",
    277                                     spi);
    278                 } catch (ServiceSpecificException e) {
    279                     // FIXME: get the error code and throw is at an IOException from Errno Exception
    280                 } catch (RemoteException e) {
    281                     Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
    282                 }
    283             }
    284 
    285             for (int direction : DIRECTIONS) {
    286                 mSpis[direction].removeReference();
    287             }
    288 
    289             if (mSocket != null) {
    290                 mSocket.removeReference();
    291             }
    292         }
    293     }
    294 
    295     private final class SpiRecord extends ManagedResource {
    296         private final int mDirection;
    297         private final String mLocalAddress;
    298         private final String mRemoteAddress;
    299         private int mSpi;
    300 
    301         private boolean mOwnedByTransform = false;
    302 
    303         SpiRecord(
    304                 int resourceId,
    305                 IBinder binder,
    306                 int direction,
    307                 String localAddress,
    308                 String remoteAddress,
    309                 int spi) {
    310             super(resourceId, binder);
    311             mDirection = direction;
    312             mLocalAddress = localAddress;
    313             mRemoteAddress = remoteAddress;
    314             mSpi = spi;
    315         }
    316 
    317         /** always guarded by IpSecService#this */
    318         @Override
    319         protected void releaseResources() {
    320             if (mOwnedByTransform) {
    321                 Log.d(TAG, "Cannot release Spi " + mSpi + ": Currently locked by a Transform");
    322                 // Because SPIs are "handed off" to transform, objects, they should never be
    323                 // freed from the SpiRecord once used in a transform. (They refer to the same SA,
    324                 // thus ownership and responsibility for freeing these resources passes to the
    325                 // Transform object). Thus, we should let the user free them without penalty once
    326                 // they are applied in a Transform object.
    327                 return;
    328             }
    329 
    330             try {
    331                 getNetdInstance()
    332                         .ipSecDeleteSecurityAssociation(
    333                                 mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi);
    334             } catch (ServiceSpecificException e) {
    335                 // FIXME: get the error code and throw is at an IOException from Errno Exception
    336             } catch (RemoteException e) {
    337                 Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId);
    338             }
    339 
    340             mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
    341         }
    342 
    343         public int getSpi() {
    344             return mSpi;
    345         }
    346 
    347         public void setOwnedByTransform() {
    348             if (mOwnedByTransform) {
    349                 // Programming error
    350                 throw new IllegalStateException("Cannot own an SPI twice!");
    351             }
    352 
    353             mOwnedByTransform = true;
    354         }
    355     }
    356 
    357     private final class UdpSocketRecord extends ManagedResource {
    358         private FileDescriptor mSocket;
    359         private final int mPort;
    360 
    361         UdpSocketRecord(int resourceId, IBinder binder, FileDescriptor socket, int port) {
    362             super(resourceId, binder);
    363             mSocket = socket;
    364             mPort = port;
    365         }
    366 
    367         /** always guarded by IpSecService#this */
    368         @Override
    369         protected void releaseResources() {
    370             Log.d(TAG, "Closing port " + mPort);
    371             IoUtils.closeQuietly(mSocket);
    372             mSocket = null;
    373         }
    374 
    375         public int getPort() {
    376             return mPort;
    377         }
    378 
    379         public FileDescriptor getSocket() {
    380             return mSocket;
    381         }
    382     }
    383 
    384     /**
    385      * Constructs a new IpSecService instance
    386      *
    387      * @param context Binder context for this service
    388      */
    389     private IpSecService(Context context) {
    390         mContext = context;
    391     }
    392 
    393     static IpSecService create(Context context) throws InterruptedException {
    394         final IpSecService service = new IpSecService(context);
    395         service.connectNativeNetdService();
    396         return service;
    397     }
    398 
    399     public void systemReady() {
    400         if (isNetdAlive()) {
    401             Slog.d(TAG, "IpSecService is ready");
    402         } else {
    403             Slog.wtf(TAG, "IpSecService not ready: failed to connect to NetD Native Service!");
    404         }
    405     }
    406 
    407     private void connectNativeNetdService() {
    408         // Avoid blocking the system server to do this
    409         new Thread() {
    410             @Override
    411             public void run() {
    412                 synchronized (IpSecService.this) {
    413                     NetdService.get(NETD_FETCH_TIMEOUT);
    414                 }
    415             }
    416         }.start();
    417     }
    418 
    419     INetd getNetdInstance() throws RemoteException {
    420         final INetd netd = NetdService.getInstance();
    421         if (netd == null) {
    422             throw new RemoteException("Failed to Get Netd Instance");
    423         }
    424         return netd;
    425     }
    426 
    427     synchronized boolean isNetdAlive() {
    428         try {
    429             final INetd netd = getNetdInstance();
    430             if (netd == null) {
    431                 return false;
    432             }
    433             return netd.isAlive();
    434         } catch (RemoteException re) {
    435             return false;
    436         }
    437     }
    438 
    439     @Override
    440     /** Get a new SPI and maintain the reservation in the system server */
    441     public synchronized IpSecSpiResponse reserveSecurityParameterIndex(
    442             int direction, String remoteAddress, int requestedSpi, IBinder binder)
    443             throws RemoteException {
    444         int resourceId = mNextResourceId.getAndIncrement();
    445 
    446         int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
    447         String localAddress = "";
    448         try {
    449             spi =
    450                     getNetdInstance()
    451                             .ipSecAllocateSpi(
    452                                     resourceId,
    453                                     direction,
    454                                     localAddress,
    455                                     remoteAddress,
    456                                     requestedSpi);
    457             Log.d(TAG, "Allocated SPI " + spi);
    458             mSpiRecords.put(
    459                     resourceId,
    460                     new SpiRecord(resourceId, binder, direction, localAddress, remoteAddress, spi));
    461         } catch (ServiceSpecificException e) {
    462             // TODO: Add appropriate checks when other ServiceSpecificException types are supported
    463             return new IpSecSpiResponse(
    464                     IpSecManager.Status.SPI_UNAVAILABLE, IpSecManager.INVALID_RESOURCE_ID, spi);
    465         } catch (RemoteException e) {
    466             throw e.rethrowFromSystemServer();
    467         }
    468         return new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, spi);
    469     }
    470 
    471     /* This method should only be called from Binder threads. Do not call this from
    472      * within the system server as it will crash the system on failure.
    473      */
    474     private synchronized <T extends ManagedResource> void releaseManagedResource(
    475             ManagedResourceArray<T> resArray, int resourceId, String typeName)
    476             throws RemoteException {
    477         // We want to non-destructively get so that we can check credentials before removing
    478         // this from the records.
    479         T record = resArray.get(resourceId);
    480 
    481         if (record == null) {
    482             throw new IllegalArgumentException(
    483                     typeName + " " + resourceId + " is not available to be deleted");
    484         }
    485 
    486         record.release();
    487         resArray.remove(resourceId);
    488     }
    489 
    490     /** Release a previously allocated SPI that has been registered with the system server */
    491     @Override
    492     public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
    493         releaseManagedResource(mSpiRecords, resourceId, "SecurityParameterIndex");
    494     }
    495 
    496     /**
    497      * This function finds and forcibly binds to a random system port, ensuring that the port cannot
    498      * be unbound.
    499      *
    500      * <p>A socket cannot be un-bound from a port if it was bound to that port by number. To select
    501      * a random open port and then bind by number, this function creates a temp socket, binds to a
    502      * random port (specifying 0), gets that port number, and then uses is to bind the user's UDP
    503      * Encapsulation Socket forcibly, so that it cannot be un-bound by the user with the returned
    504      * FileHandle.
    505      *
    506      * <p>The loop in this function handles the inherent race window between un-binding to a port
    507      * and re-binding, during which the system could *technically* hand that port out to someone
    508      * else.
    509      */
    510     private void bindToRandomPort(FileDescriptor sockFd) throws IOException {
    511         for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
    512             try {
    513                 FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    514                 Os.bind(probeSocket, INADDR_ANY, 0);
    515                 int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
    516                 Os.close(probeSocket);
    517                 Log.v(TAG, "Binding to port " + port);
    518                 Os.bind(sockFd, INADDR_ANY, port);
    519                 return;
    520             } catch (ErrnoException e) {
    521                 // Someone miraculously claimed the port just after we closed probeSocket.
    522                 if (e.errno == OsConstants.EADDRINUSE) {
    523                     continue;
    524                 }
    525                 throw e.rethrowAsIOException();
    526             }
    527         }
    528         throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
    529     }
    530 
    531     /**
    532      * Open a socket via the system server and bind it to the specified port (random if port=0).
    533      * This will return a PFD to the user that represent a bound UDP socket. The system server will
    534      * cache the socket and a record of its owner so that it can and must be freed when no longer
    535      * needed.
    536      */
    537     @Override
    538     public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
    539             throws RemoteException {
    540         if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) {
    541             throw new IllegalArgumentException(
    542                     "Specified port number must be a valid non-reserved UDP port");
    543         }
    544         int resourceId = mNextResourceId.getAndIncrement();
    545         FileDescriptor sockFd = null;
    546         try {
    547             sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    548 
    549             if (port != 0) {
    550                 Log.v(TAG, "Binding to port " + port);
    551                 Os.bind(sockFd, INADDR_ANY, port);
    552             } else {
    553                 bindToRandomPort(sockFd);
    554             }
    555             // This code is common to both the unspecified and specified port cases
    556             Os.setsockoptInt(
    557                     sockFd,
    558                     OsConstants.IPPROTO_UDP,
    559                     OsConstants.UDP_ENCAP,
    560                     OsConstants.UDP_ENCAP_ESPINUDP);
    561 
    562             mUdpSocketRecords.put(
    563                     resourceId, new UdpSocketRecord(resourceId, binder, sockFd, port));
    564             return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd);
    565         } catch (IOException | ErrnoException e) {
    566             IoUtils.closeQuietly(sockFd);
    567         }
    568         // If we make it to here, then something has gone wrong and we couldn't open a socket.
    569         // The only reasonable condition that would cause that is resource unavailable.
    570         return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
    571     }
    572 
    573     /** close a socket that has been been allocated by and registered with the system server */
    574     @Override
    575     public void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {
    576 
    577         releaseManagedResource(mUdpSocketRecords, resourceId, "UdpEncapsulationSocket");
    578     }
    579 
    580     /**
    581      * Create a transport mode transform, which represent two security associations (one in each
    582      * direction) in the kernel. The transform will be cached by the system server and must be freed
    583      * when no longer needed. It is possible to free one, deleting the SA from underneath sockets
    584      * that are using it, which will result in all of those sockets becoming unable to send or
    585      * receive data.
    586      */
    587     @Override
    588     public synchronized IpSecTransformResponse createTransportModeTransform(
    589             IpSecConfig c, IBinder binder) throws RemoteException {
    590         int resourceId = mNextResourceId.getAndIncrement();
    591         SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
    592         // TODO: Basic input validation here since it's coming over the Binder
    593         int encapType, encapLocalPort = 0, encapRemotePort = 0;
    594         UdpSocketRecord socketRecord = null;
    595         encapType = c.getEncapType();
    596         if (encapType != IpSecTransform.ENCAP_NONE) {
    597             socketRecord = mUdpSocketRecords.get(c.getEncapLocalResourceId());
    598             encapLocalPort = socketRecord.getPort();
    599             encapRemotePort = c.getEncapRemotePort();
    600         }
    601 
    602         for (int direction : DIRECTIONS) {
    603             IpSecAlgorithm auth = c.getAuthentication(direction);
    604             IpSecAlgorithm crypt = c.getEncryption(direction);
    605 
    606             spis[direction] = mSpiRecords.get(c.getSpiResourceId(direction));
    607             int spi = spis[direction].getSpi();
    608             try {
    609                 getNetdInstance()
    610                         .ipSecAddSecurityAssociation(
    611                                 resourceId,
    612                                 c.getMode(),
    613                                 direction,
    614                                 (c.getLocalAddress() != null)
    615                                         ? c.getLocalAddress().getHostAddress()
    616                                         : "",
    617                                 (c.getRemoteAddress() != null)
    618                                         ? c.getRemoteAddress().getHostAddress()
    619                                         : "",
    620                                 (c.getNetwork() != null)
    621                                         ? c.getNetwork().getNetworkHandle()
    622                                         : 0,
    623                                 spi,
    624                                 (auth != null) ? auth.getName() : "",
    625                                 (auth != null) ? auth.getKey() : null,
    626                                 (auth != null) ? auth.getTruncationLengthBits() : 0,
    627                                 (crypt != null) ? crypt.getName() : "",
    628                                 (crypt != null) ? crypt.getKey() : null,
    629                                 (crypt != null) ? crypt.getTruncationLengthBits() : 0,
    630                                 encapType,
    631                                 encapLocalPort,
    632                                 encapRemotePort);
    633             } catch (ServiceSpecificException e) {
    634                 // FIXME: get the error code and throw is at an IOException from Errno Exception
    635                 return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
    636             }
    637         }
    638         // Both SAs were created successfully, time to construct a record and lock it away
    639         mTransformRecords.put(
    640                 resourceId, new TransformRecord(resourceId, binder, c, spis, socketRecord));
    641         return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
    642     }
    643 
    644     /**
    645      * Delete a transport mode transform that was previously allocated by + registered with the
    646      * system server. If this is called on an inactive (or non-existent) transform, it will not
    647      * return an error. It's safe to de-allocate transforms that may have already been deleted for
    648      * other reasons.
    649      */
    650     @Override
    651     public void deleteTransportModeTransform(int resourceId) throws RemoteException {
    652         releaseManagedResource(mTransformRecords, resourceId, "IpSecTransform");
    653     }
    654 
    655     /**
    656      * Apply an active transport mode transform to a socket, which will apply the IPsec security
    657      * association as a correspondent policy to the provided socket
    658      */
    659     @Override
    660     public synchronized void applyTransportModeTransform(
    661             ParcelFileDescriptor socket, int resourceId) throws RemoteException {
    662         // Synchronize liberally here because we are using ManagedResources in this block
    663         TransformRecord info;
    664         // FIXME: this code should be factored out into a security check + getter
    665         info = mTransformRecords.get(resourceId);
    666 
    667         if (info == null) {
    668             throw new IllegalArgumentException("Transform " + resourceId + " is not active");
    669         }
    670 
    671         // TODO: make this a function.
    672         if (info.pid != getCallingPid() || info.uid != getCallingUid()) {
    673             throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
    674         }
    675 
    676         IpSecConfig c = info.getConfig();
    677         try {
    678             for (int direction : DIRECTIONS) {
    679                 getNetdInstance()
    680                         .ipSecApplyTransportModeTransform(
    681                                 socket.getFileDescriptor(),
    682                                 resourceId,
    683                                 direction,
    684                                 (c.getLocalAddress() != null)
    685                                         ? c.getLocalAddress().getHostAddress()
    686                                         : "",
    687                                 (c.getRemoteAddress() != null)
    688                                         ? c.getRemoteAddress().getHostAddress()
    689                                         : "",
    690                                 info.getSpiRecord(direction).getSpi());
    691             }
    692         } catch (ServiceSpecificException e) {
    693             // FIXME: get the error code and throw is at an IOException from Errno Exception
    694         }
    695     }
    696 
    697     /**
    698      * Remove a transport mode transform from a socket, applying the default (empty) policy. This
    699      * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of
    700      * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not
    701      * used: reserved for future improved input validation.
    702      */
    703     @Override
    704     public void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
    705             throws RemoteException {
    706         try {
    707             getNetdInstance().ipSecRemoveTransportModeTransform(socket.getFileDescriptor());
    708         } catch (ServiceSpecificException e) {
    709             // FIXME: get the error code and throw is at an IOException from Errno Exception
    710         }
    711     }
    712 
    713     @Override
    714     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    715         mContext.enforceCallingOrSelfPermission(DUMP, TAG);
    716         // TODO: Add dump code to print out a log of all the resources being tracked
    717         pw.println("IpSecService Log:");
    718         pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
    719         pw.println();
    720     }
    721 }
    722