IpSecTransform.java revision ac11ccb1f66d5dadb6c6fd1d47408e36c48c94ce
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 static android.net.IpSecManager.INVALID_RESOURCE_ID;
19import static android.net.IpSecManager.KEY_RESOURCE_ID;
20import static android.net.IpSecManager.KEY_STATUS;
21
22import android.annotation.IntDef;
23import android.annotation.NonNull;
24import android.annotation.SystemApi;
25import android.content.Context;
26import android.os.Binder;
27import android.os.Bundle;
28import android.os.IBinder;
29import android.os.RemoteException;
30import android.os.ServiceManager;
31import android.util.Log;
32import com.android.internal.util.Preconditions;
33import dalvik.system.CloseGuard;
34import java.io.IOException;
35import java.lang.annotation.Retention;
36import java.lang.annotation.RetentionPolicy;
37import 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 */
51public 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