Home | History | Annotate | Download | only in net
      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 package android.net;
     17 
     18 import static android.net.IpSecManager.INVALID_RESOURCE_ID;
     19 import static android.net.IpSecManager.KEY_RESOURCE_ID;
     20 import static android.net.IpSecManager.KEY_STATUS;
     21 
     22 import android.annotation.IntDef;
     23 import android.annotation.NonNull;
     24 import android.annotation.SystemApi;
     25 import android.content.Context;
     26 import android.os.Binder;
     27 import android.os.Bundle;
     28 import android.os.IBinder;
     29 import android.os.RemoteException;
     30 import android.os.ServiceManager;
     31 import android.util.Log;
     32 import com.android.internal.util.Preconditions;
     33 import dalvik.system.CloseGuard;
     34 import java.io.IOException;
     35 import java.lang.annotation.Retention;
     36 import java.lang.annotation.RetentionPolicy;
     37 import java.net.InetAddress;
     38 
     39 /**
     40  * This class represents an IpSecTransform, which encapsulates both properties and state of IPsec.
     41  *
     42  * <p>IpSecTransforms must be built from an IpSecTransform.Builder, and they must persist throughout
     43  * the lifetime of the underlying transform. If a transform object leaves scope, the underlying
     44  * transform may be disabled automatically, with likely undesirable results.
     45  *
     46  * <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array
     47  * of traffic or may represent a transport mode transform operating on a Socket or Sockets.
     48  *
     49  * @hide
     50  */
     51 public final class IpSecTransform implements AutoCloseable {
     52     private static final String TAG = "IpSecTransform";
     53 
     54     /**
     55      * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies
     56      * to traffic towards the host.
     57      */
     58     public static final int DIRECTION_IN = 0;
     59 
     60     /**
     61      * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies
     62      * to traffic from the host.
     63      */
     64     public static final int DIRECTION_OUT = 1;
     65 
     66     /** @hide */
     67     @IntDef(value = {DIRECTION_IN, DIRECTION_OUT})
     68     @Retention(RetentionPolicy.SOURCE)
     69     public @interface TransformDirection {}
     70 
     71     /** @hide */
     72     private static final int MODE_TUNNEL = 0;
     73 
     74     /** @hide */
     75     private static final int MODE_TRANSPORT = 1;
     76 
     77     /** @hide */
     78     public static final int ENCAP_NONE = 0;
     79 
     80     /**
     81      * IpSec traffic will be encapsulated within UDP as per <a
     82      * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>.
     83      *
     84      * @hide
     85      */
     86     public static final int ENCAP_ESPINUDP = 1;
     87 
     88     /**
     89      * IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad
     90      * (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP.
     91      *
     92      * @hide
     93      */
     94     public static final int ENCAP_ESPINUDP_NONIKE = 2;
     95 
     96     /** @hide */
     97     @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NONIKE})
     98     @Retention(RetentionPolicy.SOURCE)
     99     public @interface EncapType {}
    100 
    101     private IpSecTransform(Context context, IpSecConfig config) {
    102         mContext = context;
    103         mConfig = config;
    104         mResourceId = INVALID_RESOURCE_ID;
    105     }
    106 
    107     private IIpSecService getIpSecService() {
    108         IBinder b = ServiceManager.getService(android.content.Context.IPSEC_SERVICE);
    109         if (b == null) {
    110             throw new RemoteException("Failed to connect to IpSecService")
    111                     .rethrowAsRuntimeException();
    112         }
    113 
    114         return IIpSecService.Stub.asInterface(b);
    115     }
    116 
    117     private void checkResultStatusAndThrow(int status)
    118             throws IOException, IpSecManager.ResourceUnavailableException,
    119                     IpSecManager.SpiUnavailableException {
    120         switch (status) {
    121             case IpSecManager.Status.OK:
    122                 return;
    123                 // TODO: Pass Error string back from bundle so that errors can be more specific
    124             case IpSecManager.Status.RESOURCE_UNAVAILABLE:
    125                 throw new IpSecManager.ResourceUnavailableException(
    126                         "Failed to allocate a new IpSecTransform");
    127             case IpSecManager.Status.SPI_UNAVAILABLE:
    128                 Log.wtf(TAG, "Attempting to use an SPI that was somehow not reserved");
    129                 // Fall through
    130             default:
    131                 throw new IllegalStateException(
    132                         "Failed to Create a Transform with status code " + status);
    133         }
    134     }
    135 
    136     private IpSecTransform activate()
    137             throws IOException, IpSecManager.ResourceUnavailableException,
    138                     IpSecManager.SpiUnavailableException {
    139         synchronized (this) {
    140             try {
    141                 IIpSecService svc = getIpSecService();
    142                 Bundle result = svc.createTransportModeTransform(mConfig, new Binder());
    143                 int status = result.getInt(KEY_STATUS);
    144                 checkResultStatusAndThrow(status);
    145                 mResourceId = result.getInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID);
    146 
    147                 /* Keepalive will silently fail if not needed by the config; but, if needed and
    148                  * it fails to start, we need to bail because a transform will not be reliable
    149                  * to use if keepalive is expected to offload and fails.
    150                  */
    151                 // FIXME: if keepalive fails, we need to fail spectacularly
    152                 startKeepalive(mContext);
    153                 Log.d(TAG, "Added Transform with Id " + mResourceId);
    154                 mCloseGuard.open("build");
    155             } catch (RemoteException e) {
    156                 throw e.rethrowAsRuntimeException();
    157             }
    158         }
    159 
    160         return this;
    161     }
    162 
    163     /**
    164      * Deactivate an IpSecTransform and free all resources for that transform that are managed by
    165      * the system for this Transform.
    166      *
    167      * <p>Deactivating a transform while it is still applied to any Socket will result in sockets
    168      * refusing to send or receive data. This method will silently succeed if the specified
    169      * transform has already been removed; thus, it is always safe to attempt cleanup when a
    170      * transform is no longer needed.
    171      */
    172     public void close() {
    173         Log.d(TAG, "Removing Transform with Id " + mResourceId);
    174 
    175         // Always safe to attempt cleanup
    176         if (mResourceId == INVALID_RESOURCE_ID) {
    177             mCloseGuard.close();
    178             return;
    179         }
    180         try {
    181             /* Order matters here because the keepalive is best-effort but could fail in some
    182              * horrible way to be removed if the wifi (or cell) subsystem has crashed, and we
    183              * still want to clear out the transform.
    184              */
    185             IIpSecService svc = getIpSecService();
    186             svc.deleteTransportModeTransform(mResourceId);
    187             stopKeepalive();
    188         } catch (RemoteException e) {
    189             throw e.rethrowAsRuntimeException();
    190         } finally {
    191             mResourceId = INVALID_RESOURCE_ID;
    192             mCloseGuard.close();
    193         }
    194     }
    195 
    196     @Override
    197     protected void finalize() throws Throwable {
    198         if (mCloseGuard != null) {
    199             mCloseGuard.warnIfOpen();
    200         }
    201         close();
    202     }
    203 
    204     /* Package */
    205     IpSecConfig getConfig() {
    206         return mConfig;
    207     }
    208 
    209     private final IpSecConfig mConfig;
    210     private int mResourceId;
    211     private final Context mContext;
    212     private final CloseGuard mCloseGuard = CloseGuard.get();
    213     private ConnectivityManager.PacketKeepalive mKeepalive;
    214     private int mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE;
    215     private Object mKeepaliveSyncLock = new Object();
    216     private ConnectivityManager.PacketKeepaliveCallback mKeepaliveCallback =
    217             new ConnectivityManager.PacketKeepaliveCallback() {
    218 
    219                 @Override
    220                 public void onStarted() {
    221                     synchronized (mKeepaliveSyncLock) {
    222                         mKeepaliveStatus = ConnectivityManager.PacketKeepalive.SUCCESS;
    223                         mKeepaliveSyncLock.notifyAll();
    224                     }
    225                 }
    226 
    227                 @Override
    228                 public void onStopped() {
    229                     synchronized (mKeepaliveSyncLock) {
    230                         mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE;
    231                         mKeepaliveSyncLock.notifyAll();
    232                     }
    233                 }
    234 
    235                 @Override
    236                 public void onError(int error) {
    237                     synchronized (mKeepaliveSyncLock) {
    238                         mKeepaliveStatus = error;
    239                         mKeepaliveSyncLock.notifyAll();
    240                     }
    241                 }
    242             };
    243 
    244     /* Package */
    245     void startKeepalive(Context c) {
    246         // FIXME: NO_KEEPALIVE needs to be a constant
    247         if (mConfig.getNattKeepaliveInterval() == 0) {
    248             return;
    249         }
    250 
    251         ConnectivityManager cm =
    252                 (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
    253 
    254         if (mKeepalive != null) {
    255             Log.wtf(TAG, "Keepalive already started for this IpSecTransform.");
    256             return;
    257         }
    258 
    259         synchronized (mKeepaliveSyncLock) {
    260             mKeepalive =
    261                     cm.startNattKeepalive(
    262                             mConfig.getNetwork(),
    263                             mConfig.getNattKeepaliveInterval(),
    264                             mKeepaliveCallback,
    265                             mConfig.getLocalAddress(),
    266                             mConfig.getEncapLocalPort(),
    267                             mConfig.getRemoteAddress());
    268             try {
    269                 // FIXME: this is still a horrible way to fudge the synchronous callback
    270                 mKeepaliveSyncLock.wait(2000);
    271             } catch (InterruptedException e) {
    272             }
    273         }
    274         if (mKeepaliveStatus != ConnectivityManager.PacketKeepalive.SUCCESS) {
    275             throw new UnsupportedOperationException("Packet Keepalive cannot be started");
    276         }
    277     }
    278 
    279     /* Package */
    280     int getResourceId() {
    281         return mResourceId;
    282     }
    283 
    284     /* Package */
    285     void stopKeepalive() {
    286         if (mKeepalive == null) {
    287             return;
    288         }
    289         mKeepalive.stop();
    290         synchronized (mKeepaliveSyncLock) {
    291             if (mKeepaliveStatus == ConnectivityManager.PacketKeepalive.SUCCESS) {
    292                 try {
    293                     mKeepaliveSyncLock.wait(2000);
    294                 } catch (InterruptedException e) {
    295                 }
    296             }
    297         }
    298     }
    299 
    300     /**
    301      * Builder object to facilitate the creation of IpSecTransform objects.
    302      *
    303      * <p>Apply additional properties to the transform and then call a build() method to return an
    304      * IpSecTransform object.
    305      *
    306      * @see Builder#buildTransportModeTransform(InetAddress)
    307      */
    308     public static class Builder {
    309         private Context mContext;
    310         private IpSecConfig mConfig;
    311 
    312         /**
    313          * Add an encryption algorithm to the transform for the given direction.
    314          *
    315          * <p>If encryption is set for a given direction without also providing an SPI for that
    316          * direction, creation of an IpSecTransform will fail upon calling a build() method.
    317          *
    318          * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
    319          * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
    320          */
    321         public IpSecTransform.Builder setEncryption(
    322                 @TransformDirection int direction, IpSecAlgorithm algo) {
    323             mConfig.flow[direction].encryption = algo;
    324             return this;
    325         }
    326 
    327         /**
    328          * Add an authentication/integrity algorithm to the transform.
    329          *
    330          * <p>If authentication is set for a given direction without also providing an SPI for that
    331          * direction, creation of an IpSecTransform will fail upon calling a build() method.
    332          *
    333          * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
    334          * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
    335          */
    336         public IpSecTransform.Builder setAuthentication(
    337                 @TransformDirection int direction, IpSecAlgorithm algo) {
    338             mConfig.flow[direction].authentication = algo;
    339             return this;
    340         }
    341 
    342         /**
    343          * Set the SPI, which uniquely identifies a particular IPsec session from others. Because
    344          * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a
    345          * given destination address.
    346          *
    347          * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as
    348          * possible. To reserve a value call {@link IpSecManager#reserveSecurityParameterIndex(int,
    349          * InetAddress, int)}. Otherwise, SPI collisions would prevent a transform from being
    350          * activated. IpSecManager#reserveSecurityParameterIndex(int, InetAddres$s, int)}.
    351          *
    352          * <p>Unless an SPI is set for a given direction, traffic in that direction will be
    353          * sent/received without any IPsec applied.
    354          *
    355          * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
    356          * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
    357          *     traffic
    358          */
    359         public IpSecTransform.Builder setSpi(
    360                 @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) {
    361             // TODO: convert to using the resource Id of the SPI. Then build() can validate
    362             // the owner in the IpSecService
    363             mConfig.flow[direction].spi = spi.getSpi();
    364             return this;
    365         }
    366 
    367         /**
    368          * Specify the network on which this transform will emit its traffic; (otherwise it will
    369          * emit on the default network).
    370          *
    371          * <p>Restricts the transformed traffic to a particular {@link Network}. This is required in
    372          * tunnel mode.
    373          *
    374          * @hide
    375          */
    376         @SystemApi
    377         public IpSecTransform.Builder setUnderlyingNetwork(Network net) {
    378             mConfig.network = net;
    379             return this;
    380         }
    381 
    382         /**
    383          * Add UDP encapsulation to an IPv4 transform
    384          *
    385          * <p>This option allows IPsec traffic to pass through NAT. Refer to RFC 3947 and 3948 for
    386          * details on how UDP should be applied to IPsec.
    387          *
    388          * @param localSocket a {@link IpSecManager.UdpEncapsulationSocket} for sending and
    389          *     receiving encapsulating traffic.
    390          * @param remotePort the UDP port number of the remote that will send and receive
    391          *     encapsulated traffic. In the case of IKE, this is likely port 4500.
    392          */
    393         public IpSecTransform.Builder setIpv4Encapsulation(
    394                 IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
    395             // TODO: check encap type is valid.
    396             mConfig.encapType = ENCAP_ESPINUDP;
    397             mConfig.encapLocalPort = localSocket.getPort(); // TODO: plug in the encap socket
    398             mConfig.encapRemotePort = remotePort;
    399             return this;
    400         }
    401 
    402         // TODO: Decrease the minimum keepalive to maybe 10?
    403         // TODO: Probably a better exception to throw for NATTKeepalive failure
    404         // TODO: Specify the needed NATT keepalive permission.
    405         /**
    406          * Send a NATT Keepalive packet with a given maximum interval. This will create an offloaded
    407          * request to do power-efficient NATT Keepalive. If NATT keepalive is requested but cannot
    408          * be activated, then the transform will fail to activate and throw an IOException.
    409          *
    410          * @param intervalSeconds the maximum number of seconds between keepalive packets, no less
    411          *     than 20s and no more than 3600s.
    412          * @hide
    413          */
    414         @SystemApi
    415         public IpSecTransform.Builder setNattKeepalive(int intervalSeconds) {
    416             mConfig.nattKeepaliveInterval = intervalSeconds;
    417             return this;
    418         }
    419 
    420         /**
    421          * Build and return an active {@link IpSecTransform} object as a Transport Mode Transform.
    422          * Some parameters have interdependencies that are checked at build time. If a well-formed
    423          * transform cannot be created from the supplied parameters, this method will throw an
    424          * Exception.
    425          *
    426          * <p>Upon a successful return from this call, the provided IpSecTransform will be active
    427          * and may be applied to sockets. If too many IpSecTransform objects are active for a given
    428          * user this operation will fail and throw ResourceUnavailableException. To avoid these
    429          * exceptions, unused Transform objects must be cleaned up by calling {@link
    430          * IpSecTransform#close()} when they are no longer needed.
    431          *
    432          * @param remoteAddress the {@link InetAddress} that, when matched on traffic to/from this
    433          *     socket will cause the transform to be applied.
    434          *     <p>Note that an active transform will not impact any network traffic until it has
    435          *     been applied to one or more Sockets. Calling this method is a necessary precondition
    436          *     for applying it to a socket, but is not sufficient to actually apply IPsec.
    437          * @throws IllegalArgumentException indicating that a particular combination of transform
    438          *     properties is invalid.
    439          * @throws IpSecManager.ResourceUnavailableException in the event that no more Transforms
    440          *     may be allocated
    441          * @throws SpiUnavailableException if the SPI collides with an existing transform
    442          *     (unlikely).
    443          * @throws ResourceUnavailableException if the current user currently has exceeded the
    444          *     number of allowed active transforms.
    445          */
    446         public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress)
    447                 throws IpSecManager.ResourceUnavailableException,
    448                         IpSecManager.SpiUnavailableException, IOException {
    449             //FIXME: argument validation here
    450             //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation");
    451             mConfig.mode = MODE_TRANSPORT;
    452             mConfig.remoteAddress = remoteAddress;
    453             return new IpSecTransform(mContext, mConfig).activate();
    454         }
    455 
    456         /**
    457          * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some
    458          * parameters have interdependencies that are checked at build time.
    459          *
    460          * @param localAddress the {@link InetAddress} that provides the local endpoint for this
    461          *     IPsec tunnel. This is almost certainly an address belonging to the {@link Network}
    462          *     that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}.
    463          * @param remoteAddress the {@link InetAddress} representing the remote endpoint of this
    464          *     IPsec tunnel.
    465          * @throws IllegalArgumentException indicating that a particular combination of transform
    466          *     properties is invalid.
    467          * @hide
    468          */
    469         public IpSecTransform buildTunnelModeTransform(
    470                 InetAddress localAddress, InetAddress remoteAddress) {
    471             //FIXME: argument validation here
    472             //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation");
    473             mConfig.localAddress = localAddress;
    474             mConfig.remoteAddress = remoteAddress;
    475             mConfig.mode = MODE_TUNNEL;
    476             return new IpSecTransform(mContext, mConfig);
    477         }
    478 
    479         /**
    480          * Create a new IpSecTransform.Builder to construct an IpSecTransform
    481          *
    482          * @param context current Context
    483          */
    484         public Builder(@NonNull Context context) {
    485             Preconditions.checkNotNull(context);
    486             mContext = context;
    487             mConfig = new IpSecConfig();
    488         }
    489     }
    490 }
    491