blob: f72cbc9d68aff1183cdb52a93978824a8bc47e38 [file] [log] [blame]
Nathan Harold1afbef42017-03-01 18:55:06 -08001/*
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
17package com.android.server;
18
19import static android.Manifest.permission.DUMP;
Nathan Harold93962f32017-03-07 13:23:36 -080020import static android.net.IpSecManager.INVALID_RESOURCE_ID;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070021import static android.system.OsConstants.AF_INET;
22import static android.system.OsConstants.IPPROTO_UDP;
23import static android.system.OsConstants.SOCK_DGRAM;
24import static com.android.internal.util.Preconditions.checkNotNull;
Nathan Harold1afbef42017-03-01 18:55:06 -080025
26import android.content.Context;
27import android.net.IIpSecService;
28import android.net.INetd;
Nathan Harold93962f32017-03-07 13:23:36 -080029import android.net.IpSecAlgorithm;
30import android.net.IpSecConfig;
31import android.net.IpSecManager;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070032import android.net.IpSecSpiResponse;
Nathan Harold93962f32017-03-07 13:23:36 -080033import android.net.IpSecTransform;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070034import android.net.IpSecTransformResponse;
35import android.net.IpSecUdpEncapResponse;
Nathan Harold1afbef42017-03-01 18:55:06 -080036import android.net.util.NetdService;
Nathan Harold93962f32017-03-07 13:23:36 -080037import android.os.Binder;
Nathan Harold93962f32017-03-07 13:23:36 -080038import android.os.IBinder;
39import android.os.ParcelFileDescriptor;
Nathan Harold1afbef42017-03-01 18:55:06 -080040import android.os.RemoteException;
Nathan Harold93962f32017-03-07 13:23:36 -080041import android.os.ServiceSpecificException;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070042import android.system.ErrnoException;
43import android.system.Os;
44import android.system.OsConstants;
Nathan Harold1afbef42017-03-01 18:55:06 -080045import android.util.Log;
46import android.util.Slog;
Nathan Harold93962f32017-03-07 13:23:36 -080047import android.util.SparseArray;
48import com.android.internal.annotations.GuardedBy;
ludi1a06aa72017-05-12 09:15:00 -070049import com.android.internal.annotations.VisibleForTesting;
Nathan Harold1afbef42017-03-01 18:55:06 -080050import java.io.FileDescriptor;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070051import java.io.IOException;
Nathan Harold1afbef42017-03-01 18:55:06 -080052import java.io.PrintWriter;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070053import java.net.InetAddress;
54import java.net.InetSocketAddress;
55import java.net.UnknownHostException;
Nathan Harold93962f32017-03-07 13:23:36 -080056import java.util.concurrent.atomic.AtomicInteger;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070057import libcore.io.IoUtils;
Nathan Harold1afbef42017-03-01 18:55:06 -080058
59/** @hide */
60public class IpSecService extends IIpSecService.Stub {
61 private static final String TAG = "IpSecService";
62 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
Nathan Harold8dc1fd02017-04-04 19:37:48 -070063
Nathan Harold1afbef42017-03-01 18:55:06 -080064 private static final String NETD_SERVICE_NAME = "netd";
Nathan Harold93962f32017-03-07 13:23:36 -080065 private static final int[] DIRECTIONS =
66 new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN};
Nathan Harold1afbef42017-03-01 18:55:06 -080067
ludi1a06aa72017-05-12 09:15:00 -070068 private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms
Nathan Harold8dc1fd02017-04-04 19:37:48 -070069 private static final int MAX_PORT_BIND_ATTEMPTS = 10;
70 private static final InetAddress INADDR_ANY;
71
72 static {
73 try {
74 INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
75 } catch (UnknownHostException e) {
76 throw new RuntimeException(e);
77 }
78 }
79
80 static final int FREE_PORT_MIN = 1024; // ports 1-1023 are reserved
81 static final int PORT_MAX = 0xFFFF; // ports are an unsigned 16-bit integer
82
83 /* Binder context for this service */
Nathan Harold1afbef42017-03-01 18:55:06 -080084 private final Context mContext;
85
Nathan Harold8dc1fd02017-04-04 19:37:48 -070086 /** Should be a never-repeating global ID for resources */
87 private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
Nathan Harold1afbef42017-03-01 18:55:06 -080088
Nathan Harold8dc1fd02017-04-04 19:37:48 -070089 @GuardedBy("this")
90 private final ManagedResourceArray<SpiRecord> mSpiRecords = new ManagedResourceArray<>();
Nathan Harold1afbef42017-03-01 18:55:06 -080091
Nathan Harold8dc1fd02017-04-04 19:37:48 -070092 @GuardedBy("this")
93 private final ManagedResourceArray<TransformRecord> mTransformRecords =
94 new ManagedResourceArray<>();
Nathan Harold93962f32017-03-07 13:23:36 -080095
Nathan Harold8dc1fd02017-04-04 19:37:48 -070096 @GuardedBy("this")
97 private final ManagedResourceArray<UdpSocketRecord> mUdpSocketRecords =
98 new ManagedResourceArray<>();
99
ludi1a06aa72017-05-12 09:15:00 -0700100 interface IpSecServiceConfiguration {
101 INetd getNetdInstance() throws RemoteException;
102
103 static IpSecServiceConfiguration GETSRVINSTANCE =
104 new IpSecServiceConfiguration() {
105 @Override
106 public INetd getNetdInstance() throws RemoteException {
107 final INetd netd = NetdService.getInstance();
108 if (netd == null) {
109 throw new RemoteException("Failed to Get Netd Instance");
110 }
111 return netd;
112 }
113 };
114 }
115
116 private final IpSecServiceConfiguration mSrvConfig;
117
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700118 /**
119 * The ManagedResource class provides a facility to cleanly and reliably release system
120 * resources. It relies on two things: an IBinder that allows ManagedResource to automatically
121 * clean up in the event that the Binder dies and a user-provided resourceId that should
122 * uniquely identify the managed resource. To use this class, the user should implement the
123 * releaseResources() method that is responsible for releasing system resources when invoked.
124 */
Nathan Harold93962f32017-03-07 13:23:36 -0800125 private abstract class ManagedResource implements IBinder.DeathRecipient {
126 final int pid;
127 final int uid;
128 private IBinder mBinder;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700129 protected int mResourceId;
Nathan Harold93962f32017-03-07 13:23:36 -0800130
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700131 private AtomicInteger mReferenceCount = new AtomicInteger(0);
132
133 ManagedResource(int resourceId, IBinder binder) {
Nathan Harold93962f32017-03-07 13:23:36 -0800134 super();
135 mBinder = binder;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700136 mResourceId = resourceId;
Nathan Harold93962f32017-03-07 13:23:36 -0800137 pid = Binder.getCallingPid();
138 uid = Binder.getCallingUid();
139
140 try {
141 mBinder.linkToDeath(this, 0);
142 } catch (RemoteException e) {
143 binderDied();
144 }
145 }
146
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700147 public void addReference() {
148 mReferenceCount.incrementAndGet();
149 }
150
151 public void removeReference() {
152 if (mReferenceCount.decrementAndGet() < 0) {
153 Log.wtf(TAG, "Programming error: negative reference count");
154 }
155 }
156
157 public boolean isReferenced() {
158 return (mReferenceCount.get() > 0);
159 }
160
161 public void checkOwnerOrSystemAndThrow() {
162 if (uid != Binder.getCallingUid()
163 && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
164 throw new SecurityException("Only the owner may access managed resources!");
165 }
166 }
167
Nathan Harold93962f32017-03-07 13:23:36 -0800168 /**
169 * When this record is no longer needed for managing system resources this function should
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700170 * clean up all system resources and nullify the record. This function shall perform all
171 * necessary cleanup of the resources managed by this record.
172 *
173 * <p>NOTE: this function verifies ownership before allowing resources to be freed.
Nathan Harold93962f32017-03-07 13:23:36 -0800174 */
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700175 public final void release() throws RemoteException {
176 synchronized (IpSecService.this) {
177 if (isReferenced()) {
178 throw new IllegalStateException(
179 "Cannot release a resource that has active references!");
180 }
Nathan Harold93962f32017-03-07 13:23:36 -0800181
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700182 if (mResourceId == INVALID_RESOURCE_ID) {
183 return;
184 }
185
186 releaseResources();
187 if (mBinder != null) {
188 mBinder.unlinkToDeath(this, 0);
189 }
190 mBinder = null;
191
192 mResourceId = INVALID_RESOURCE_ID;
Nathan Harold93962f32017-03-07 13:23:36 -0800193 }
Nathan Harold93962f32017-03-07 13:23:36 -0800194 }
195
196 /**
197 * If the Binder object dies, this function is called to free the system resources that are
198 * being managed by this record and to subsequently release this record for garbage
199 * collection
200 */
201 public final void binderDied() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700202 try {
203 release();
204 } catch (Exception e) {
205 Log.e(TAG, "Failed to release resource: " + e);
206 }
Nathan Harold93962f32017-03-07 13:23:36 -0800207 }
208
209 /**
Nathan Harold93962f32017-03-07 13:23:36 -0800210 * Implement this method to release all system resources that are being protected by this
211 * record. Once the resources are released, the record should be invalidated and no longer
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700212 * used by calling release(). This should NEVER be called directly.
213 *
214 * <p>Calls to this are always guarded by IpSecService#this
Nathan Harold93962f32017-03-07 13:23:36 -0800215 */
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700216 protected abstract void releaseResources() throws RemoteException;
Nathan Harold93962f32017-03-07 13:23:36 -0800217 };
218
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700219 /**
ludi1a06aa72017-05-12 09:15:00 -0700220 * Minimal wrapper around SparseArray that performs ownership validation on element accesses.
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700221 */
222 private class ManagedResourceArray<T extends ManagedResource> {
223 SparseArray<T> mArray = new SparseArray<>();
Nathan Harold93962f32017-03-07 13:23:36 -0800224
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700225 T get(int key) {
226 T val = mArray.get(key);
Nathan Haroldb6d2eff2017-07-06 16:57:51 -0700227 // The value should never be null unless the resource doesn't exist
228 // (since we do not allow null resources to be added).
229 if (val != null) {
230 val.checkOwnerOrSystemAndThrow();
231 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700232 return val;
233 }
234
235 void put(int key, T obj) {
236 checkNotNull(obj, "Null resources cannot be added");
237 mArray.put(key, obj);
238 }
239
240 void remove(int key) {
241 mArray.remove(key);
242 }
243 }
244
245 private final class TransformRecord extends ManagedResource {
246 private final IpSecConfig mConfig;
247 private final SpiRecord[] mSpis;
248 private final UdpSocketRecord mSocket;
249
250 TransformRecord(
251 int resourceId,
252 IBinder binder,
253 IpSecConfig config,
254 SpiRecord[] spis,
255 UdpSocketRecord socket) {
256 super(resourceId, binder);
Nathan Harold93962f32017-03-07 13:23:36 -0800257 mConfig = config;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700258 mSpis = spis;
259 mSocket = socket;
260
261 for (int direction : DIRECTIONS) {
262 mSpis[direction].addReference();
263 mSpis[direction].setOwnedByTransform();
264 }
265
266 if (mSocket != null) {
267 mSocket.addReference();
268 }
Nathan Harold93962f32017-03-07 13:23:36 -0800269 }
270
271 public IpSecConfig getConfig() {
272 return mConfig;
273 }
274
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700275 public SpiRecord getSpiRecord(int direction) {
276 return mSpis[direction];
277 }
278
279 /** always guarded by IpSecService#this */
Nathan Harold93962f32017-03-07 13:23:36 -0800280 @Override
281 protected void releaseResources() {
282 for (int direction : DIRECTIONS) {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700283 int spi = mSpis[direction].getSpi();
Nathan Harold93962f32017-03-07 13:23:36 -0800284 try {
ludi1a06aa72017-05-12 09:15:00 -0700285 mSrvConfig
286 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800287 .ipSecDeleteSecurityAssociation(
288 mResourceId,
289 direction,
290 (mConfig.getLocalAddress() != null)
291 ? mConfig.getLocalAddress().getHostAddress()
292 : "",
293 (mConfig.getRemoteAddress() != null)
294 ? mConfig.getRemoteAddress().getHostAddress()
295 : "",
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700296 spi);
Nathan Harold93962f32017-03-07 13:23:36 -0800297 } catch (ServiceSpecificException e) {
298 // FIXME: get the error code and throw is at an IOException from Errno Exception
299 } catch (RemoteException e) {
300 Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
301 }
302 }
Nathan Harold93962f32017-03-07 13:23:36 -0800303
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700304 for (int direction : DIRECTIONS) {
305 mSpis[direction].removeReference();
306 }
307
308 if (mSocket != null) {
309 mSocket.removeReference();
310 }
Nathan Harold93962f32017-03-07 13:23:36 -0800311 }
312 }
313
314 private final class SpiRecord extends ManagedResource {
315 private final int mDirection;
316 private final String mLocalAddress;
317 private final String mRemoteAddress;
Nathan Harold93962f32017-03-07 13:23:36 -0800318 private int mSpi;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700319
320 private boolean mOwnedByTransform = false;
Nathan Harold93962f32017-03-07 13:23:36 -0800321
322 SpiRecord(
323 int resourceId,
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700324 IBinder binder,
Nathan Harold93962f32017-03-07 13:23:36 -0800325 int direction,
326 String localAddress,
327 String remoteAddress,
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700328 int spi) {
329 super(resourceId, binder);
Nathan Harold93962f32017-03-07 13:23:36 -0800330 mDirection = direction;
331 mLocalAddress = localAddress;
332 mRemoteAddress = remoteAddress;
333 mSpi = spi;
Nathan Harold93962f32017-03-07 13:23:36 -0800334 }
335
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700336 /** always guarded by IpSecService#this */
337 @Override
Nathan Harold93962f32017-03-07 13:23:36 -0800338 protected void releaseResources() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700339 if (mOwnedByTransform) {
340 Log.d(TAG, "Cannot release Spi " + mSpi + ": Currently locked by a Transform");
341 // Because SPIs are "handed off" to transform, objects, they should never be
342 // freed from the SpiRecord once used in a transform. (They refer to the same SA,
343 // thus ownership and responsibility for freeing these resources passes to the
344 // Transform object). Thus, we should let the user free them without penalty once
345 // they are applied in a Transform object.
346 return;
347 }
348
Nathan Harold93962f32017-03-07 13:23:36 -0800349 try {
ludi1a06aa72017-05-12 09:15:00 -0700350 mSrvConfig
351 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800352 .ipSecDeleteSecurityAssociation(
353 mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi);
354 } catch (ServiceSpecificException e) {
355 // FIXME: get the error code and throw is at an IOException from Errno Exception
356 } catch (RemoteException e) {
357 Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId);
358 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700359
360 mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
Nathan Harold93962f32017-03-07 13:23:36 -0800361 }
362
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700363 public int getSpi() {
364 return mSpi;
365 }
366
367 public void setOwnedByTransform() {
368 if (mOwnedByTransform) {
369 // Programming error
Andreas Gamped6d8e452017-07-11 10:25:09 -0700370 throw new IllegalStateException("Cannot own an SPI twice!");
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700371 }
372
373 mOwnedByTransform = true;
Nathan Harold93962f32017-03-07 13:23:36 -0800374 }
375 }
376
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700377 private final class UdpSocketRecord extends ManagedResource {
378 private FileDescriptor mSocket;
379 private final int mPort;
Nathan Harold93962f32017-03-07 13:23:36 -0800380
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700381 UdpSocketRecord(int resourceId, IBinder binder, FileDescriptor socket, int port) {
382 super(resourceId, binder);
383 mSocket = socket;
384 mPort = port;
385 }
386
387 /** always guarded by IpSecService#this */
388 @Override
389 protected void releaseResources() {
390 Log.d(TAG, "Closing port " + mPort);
391 IoUtils.closeQuietly(mSocket);
392 mSocket = null;
393 }
394
395 public int getPort() {
396 return mPort;
397 }
398
399 public FileDescriptor getSocket() {
400 return mSocket;
401 }
402 }
Nathan Harold93962f32017-03-07 13:23:36 -0800403
Nathan Harold1afbef42017-03-01 18:55:06 -0800404 /**
405 * Constructs a new IpSecService instance
406 *
407 * @param context Binder context for this service
408 */
409 private IpSecService(Context context) {
ludi1a06aa72017-05-12 09:15:00 -0700410 this(context, IpSecServiceConfiguration.GETSRVINSTANCE);
Nathan Harold1afbef42017-03-01 18:55:06 -0800411 }
412
413 static IpSecService create(Context context) throws InterruptedException {
414 final IpSecService service = new IpSecService(context);
415 service.connectNativeNetdService();
416 return service;
417 }
418
ludi1a06aa72017-05-12 09:15:00 -0700419 /** @hide */
420 @VisibleForTesting
421 public IpSecService(Context context, IpSecServiceConfiguration config) {
422 mContext = context;
423 mSrvConfig = config;
424 }
425
Nathan Harold1afbef42017-03-01 18:55:06 -0800426 public void systemReady() {
427 if (isNetdAlive()) {
428 Slog.d(TAG, "IpSecService is ready");
429 } else {
430 Slog.wtf(TAG, "IpSecService not ready: failed to connect to NetD Native Service!");
431 }
432 }
433
434 private void connectNativeNetdService() {
435 // Avoid blocking the system server to do this
Nathan Haroldb0e05082017-07-17 14:01:53 -0700436 new Thread() {
437 @Override
438 public void run() {
439 synchronized (IpSecService.this) {
ludi1a06aa72017-05-12 09:15:00 -0700440 NetdService.get(NETD_FETCH_TIMEOUT_MS);
Nathan Haroldb0e05082017-07-17 14:01:53 -0700441 }
442 }
443 }.start();
Nathan Harold1afbef42017-03-01 18:55:06 -0800444 }
445
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700446 synchronized boolean isNetdAlive() {
447 try {
ludi1a06aa72017-05-12 09:15:00 -0700448 final INetd netd = mSrvConfig.getNetdInstance();
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700449 if (netd == null) {
Nathan Harold1afbef42017-03-01 18:55:06 -0800450 return false;
451 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700452 return netd.isAlive();
453 } catch (RemoteException re) {
454 return false;
Nathan Harold1afbef42017-03-01 18:55:06 -0800455 }
456 }
457
458 @Override
Nathan Harold93962f32017-03-07 13:23:36 -0800459 /** Get a new SPI and maintain the reservation in the system server */
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700460 public synchronized IpSecSpiResponse reserveSecurityParameterIndex(
Nathan Harold93962f32017-03-07 13:23:36 -0800461 int direction, String remoteAddress, int requestedSpi, IBinder binder)
462 throws RemoteException {
463 int resourceId = mNextResourceId.getAndIncrement();
464
465 int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
466 String localAddress = "";
Nathan Harold93962f32017-03-07 13:23:36 -0800467 try {
468 spi =
ludi1a06aa72017-05-12 09:15:00 -0700469 mSrvConfig
470 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800471 .ipSecAllocateSpi(
472 resourceId,
473 direction,
474 localAddress,
475 remoteAddress,
476 requestedSpi);
477 Log.d(TAG, "Allocated SPI " + spi);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700478 mSpiRecords.put(
479 resourceId,
480 new SpiRecord(resourceId, binder, direction, localAddress, remoteAddress, spi));
Nathan Harold93962f32017-03-07 13:23:36 -0800481 } catch (ServiceSpecificException e) {
482 // TODO: Add appropriate checks when other ServiceSpecificException types are supported
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700483 return new IpSecSpiResponse(
484 IpSecManager.Status.SPI_UNAVAILABLE, IpSecManager.INVALID_RESOURCE_ID, spi);
Nathan Harold93962f32017-03-07 13:23:36 -0800485 } catch (RemoteException e) {
486 throw e.rethrowFromSystemServer();
487 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700488 return new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, spi);
489 }
490
491 /* This method should only be called from Binder threads. Do not call this from
492 * within the system server as it will crash the system on failure.
493 */
494 private synchronized <T extends ManagedResource> void releaseManagedResource(
495 ManagedResourceArray<T> resArray, int resourceId, String typeName)
496 throws RemoteException {
497 // We want to non-destructively get so that we can check credentials before removing
498 // this from the records.
499 T record = resArray.get(resourceId);
500
501 if (record == null) {
502 throw new IllegalArgumentException(
503 typeName + " " + resourceId + " is not available to be deleted");
504 }
505
506 record.release();
507 resArray.remove(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800508 }
509
510 /** Release a previously allocated SPI that has been registered with the system server */
511 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700512 public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
513 releaseManagedResource(mSpiRecords, resourceId, "SecurityParameterIndex");
514 }
515
516 /**
517 * This function finds and forcibly binds to a random system port, ensuring that the port cannot
518 * be unbound.
519 *
520 * <p>A socket cannot be un-bound from a port if it was bound to that port by number. To select
521 * a random open port and then bind by number, this function creates a temp socket, binds to a
522 * random port (specifying 0), gets that port number, and then uses is to bind the user's UDP
523 * Encapsulation Socket forcibly, so that it cannot be un-bound by the user with the returned
524 * FileHandle.
525 *
526 * <p>The loop in this function handles the inherent race window between un-binding to a port
527 * and re-binding, during which the system could *technically* hand that port out to someone
528 * else.
529 */
530 private void bindToRandomPort(FileDescriptor sockFd) throws IOException {
531 for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
532 try {
533 FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
534 Os.bind(probeSocket, INADDR_ANY, 0);
535 int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
536 Os.close(probeSocket);
537 Log.v(TAG, "Binding to port " + port);
538 Os.bind(sockFd, INADDR_ANY, port);
539 return;
540 } catch (ErrnoException e) {
541 // Someone miraculously claimed the port just after we closed probeSocket.
542 if (e.errno == OsConstants.EADDRINUSE) {
543 continue;
544 }
545 throw e.rethrowAsIOException();
546 }
547 }
548 throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
549 }
Nathan Harold93962f32017-03-07 13:23:36 -0800550
551 /**
552 * Open a socket via the system server and bind it to the specified port (random if port=0).
553 * This will return a PFD to the user that represent a bound UDP socket. The system server will
554 * cache the socket and a record of its owner so that it can and must be freed when no longer
555 * needed.
556 */
557 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700558 public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
559 throws RemoteException {
560 if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) {
561 throw new IllegalArgumentException(
562 "Specified port number must be a valid non-reserved UDP port");
563 }
564 int resourceId = mNextResourceId.getAndIncrement();
565 FileDescriptor sockFd = null;
566 try {
567 sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
568
569 if (port != 0) {
570 Log.v(TAG, "Binding to port " + port);
571 Os.bind(sockFd, INADDR_ANY, port);
572 } else {
573 bindToRandomPort(sockFd);
574 }
575 // This code is common to both the unspecified and specified port cases
576 Os.setsockoptInt(
577 sockFd,
578 OsConstants.IPPROTO_UDP,
579 OsConstants.UDP_ENCAP,
580 OsConstants.UDP_ENCAP_ESPINUDP);
581
582 mUdpSocketRecords.put(
583 resourceId, new UdpSocketRecord(resourceId, binder, sockFd, port));
584 return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd);
585 } catch (IOException | ErrnoException e) {
586 IoUtils.closeQuietly(sockFd);
587 }
588 // If we make it to here, then something has gone wrong and we couldn't open a socket.
589 // The only reasonable condition that would cause that is resource unavailable.
590 return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
Nathan Harold93962f32017-03-07 13:23:36 -0800591 }
592
593 /** close a socket that has been been allocated by and registered with the system server */
594 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700595 public void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {
596
597 releaseManagedResource(mUdpSocketRecords, resourceId, "UdpEncapsulationSocket");
598 }
Nathan Harold93962f32017-03-07 13:23:36 -0800599
600 /**
601 * Create a transport mode transform, which represent two security associations (one in each
602 * direction) in the kernel. The transform will be cached by the system server and must be freed
603 * when no longer needed. It is possible to free one, deleting the SA from underneath sockets
604 * that are using it, which will result in all of those sockets becoming unable to send or
605 * receive data.
606 */
607 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700608 public synchronized IpSecTransformResponse createTransportModeTransform(
609 IpSecConfig c, IBinder binder) throws RemoteException {
Nathan Harold93962f32017-03-07 13:23:36 -0800610 int resourceId = mNextResourceId.getAndIncrement();
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700611 SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
612 // TODO: Basic input validation here since it's coming over the Binder
613 int encapType, encapLocalPort = 0, encapRemotePort = 0;
614 UdpSocketRecord socketRecord = null;
615 encapType = c.getEncapType();
616 if (encapType != IpSecTransform.ENCAP_NONE) {
617 socketRecord = mUdpSocketRecords.get(c.getEncapLocalResourceId());
618 encapLocalPort = socketRecord.getPort();
619 encapRemotePort = c.getEncapRemotePort();
620 }
621
Nathan Harold93962f32017-03-07 13:23:36 -0800622 for (int direction : DIRECTIONS) {
623 IpSecAlgorithm auth = c.getAuthentication(direction);
624 IpSecAlgorithm crypt = c.getEncryption(direction);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700625
626 spis[direction] = mSpiRecords.get(c.getSpiResourceId(direction));
627 int spi = spis[direction].getSpi();
Nathan Harold93962f32017-03-07 13:23:36 -0800628 try {
ludi1a06aa72017-05-12 09:15:00 -0700629 mSrvConfig.getNetdInstance()
ludi0f807892017-05-20 14:15:09 -0700630 .ipSecAddSecurityAssociation(
631 resourceId,
632 c.getMode(),
633 direction,
634 (c.getLocalAddress() != null)
635 ? c.getLocalAddress().getHostAddress()
636 : "",
637 (c.getRemoteAddress() != null)
638 ? c.getRemoteAddress().getHostAddress()
639 : "",
640 (c.getNetwork() != null)
641 ? c.getNetwork().getNetworkHandle()
642 : 0,
643 spi,
644 (auth != null) ? auth.getName() : "",
645 (auth != null) ? auth.getKey() : null,
646 (auth != null) ? auth.getTruncationLengthBits() : 0,
647 (crypt != null) ? crypt.getName() : "",
648 (crypt != null) ? crypt.getKey() : null,
649 (crypt != null) ? crypt.getTruncationLengthBits() : 0,
650 encapType,
651 encapLocalPort,
652 encapRemotePort);
Nathan Harold93962f32017-03-07 13:23:36 -0800653 } catch (ServiceSpecificException e) {
654 // FIXME: get the error code and throw is at an IOException from Errno Exception
ludi0f807892017-05-20 14:15:09 -0700655 return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
Nathan Harold93962f32017-03-07 13:23:36 -0800656 }
657 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700658 // Both SAs were created successfully, time to construct a record and lock it away
659 mTransformRecords.put(
660 resourceId, new TransformRecord(resourceId, binder, c, spis, socketRecord));
661 return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800662 }
663
664 /**
665 * Delete a transport mode transform that was previously allocated by + registered with the
666 * system server. If this is called on an inactive (or non-existent) transform, it will not
667 * return an error. It's safe to de-allocate transforms that may have already been deleted for
668 * other reasons.
669 */
670 @Override
671 public void deleteTransportModeTransform(int resourceId) throws RemoteException {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700672 releaseManagedResource(mTransformRecords, resourceId, "IpSecTransform");
Nathan Harold93962f32017-03-07 13:23:36 -0800673 }
674
675 /**
676 * Apply an active transport mode transform to a socket, which will apply the IPsec security
677 * association as a correspondent policy to the provided socket
678 */
679 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700680 public synchronized void applyTransportModeTransform(
681 ParcelFileDescriptor socket, int resourceId) throws RemoteException {
682 // Synchronize liberally here because we are using ManagedResources in this block
683 TransformRecord info;
684 // FIXME: this code should be factored out into a security check + getter
685 info = mTransformRecords.get(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800686
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700687 if (info == null) {
688 throw new IllegalArgumentException("Transform " + resourceId + " is not active");
689 }
Nathan Harold93962f32017-03-07 13:23:36 -0800690
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700691 // TODO: make this a function.
692 if (info.pid != getCallingPid() || info.uid != getCallingUid()) {
693 throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
694 }
695
696 IpSecConfig c = info.getConfig();
697 try {
698 for (int direction : DIRECTIONS) {
ludi1a06aa72017-05-12 09:15:00 -0700699 mSrvConfig
700 .getNetdInstance()
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700701 .ipSecApplyTransportModeTransform(
702 socket.getFileDescriptor(),
703 resourceId,
704 direction,
705 (c.getLocalAddress() != null)
706 ? c.getLocalAddress().getHostAddress()
707 : "",
708 (c.getRemoteAddress() != null)
709 ? c.getRemoteAddress().getHostAddress()
710 : "",
711 info.getSpiRecord(direction).getSpi());
Nathan Harold93962f32017-03-07 13:23:36 -0800712 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700713 } catch (ServiceSpecificException e) {
714 // FIXME: get the error code and throw is at an IOException from Errno Exception
Nathan Harold93962f32017-03-07 13:23:36 -0800715 }
716 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700717
Nathan Harold93962f32017-03-07 13:23:36 -0800718 /**
719 * Remove a transport mode transform from a socket, applying the default (empty) policy. This
720 * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of
721 * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not
722 * used: reserved for future improved input validation.
723 */
724 @Override
725 public void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
726 throws RemoteException {
727 try {
ludi1a06aa72017-05-12 09:15:00 -0700728 mSrvConfig
729 .getNetdInstance()
730 .ipSecRemoveTransportModeTransform(socket.getFileDescriptor());
Nathan Harold93962f32017-03-07 13:23:36 -0800731 } catch (ServiceSpecificException e) {
732 // FIXME: get the error code and throw is at an IOException from Errno Exception
733 }
734 }
735
736 @Override
Nathan Harold1afbef42017-03-01 18:55:06 -0800737 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
738 mContext.enforceCallingOrSelfPermission(DUMP, TAG);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700739 // TODO: Add dump code to print out a log of all the resources being tracked
Nathan Harold1afbef42017-03-01 18:55:06 -0800740 pw.println("IpSecService Log:");
741 pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
742 pw.println();
743 }
744}