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