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