blob: bd3a8509f7a5d4e58a9b82762778ecf0032ea7b8 [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;
Nathan Harold1afbef42017-03-01 18:55:06 -080049import java.io.FileDescriptor;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070050import java.io.IOException;
Nathan Harold1afbef42017-03-01 18:55:06 -080051import java.io.PrintWriter;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070052import java.net.InetAddress;
53import java.net.InetSocketAddress;
54import java.net.UnknownHostException;
Nathan Harold93962f32017-03-07 13:23:36 -080055import java.util.concurrent.atomic.AtomicInteger;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070056import libcore.io.IoUtils;
Nathan Harold1afbef42017-03-01 18:55:06 -080057
58/** @hide */
59public class IpSecService extends IIpSecService.Stub {
60 private static final String TAG = "IpSecService";
61 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
Nathan Harold8dc1fd02017-04-04 19:37:48 -070062
Nathan Harold1afbef42017-03-01 18:55:06 -080063 private static final String NETD_SERVICE_NAME = "netd";
Nathan Harold93962f32017-03-07 13:23:36 -080064 private static final int[] DIRECTIONS =
65 new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN};
Nathan Harold1afbef42017-03-01 18:55:06 -080066
Nathan Harold8dc1fd02017-04-04 19:37:48 -070067 private static final int NETD_FETCH_TIMEOUT = 5000; //ms
68 private static final int MAX_PORT_BIND_ATTEMPTS = 10;
69 private static final InetAddress INADDR_ANY;
70
71 static {
72 try {
73 INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
74 } catch (UnknownHostException e) {
75 throw new RuntimeException(e);
76 }
77 }
78
79 static final int FREE_PORT_MIN = 1024; // ports 1-1023 are reserved
80 static final int PORT_MAX = 0xFFFF; // ports are an unsigned 16-bit integer
81
82 /* Binder context for this service */
Nathan Harold1afbef42017-03-01 18:55:06 -080083 private final Context mContext;
84
Nathan Harold8dc1fd02017-04-04 19:37:48 -070085 /** Should be a never-repeating global ID for resources */
86 private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
Nathan Harold1afbef42017-03-01 18:55:06 -080087
Nathan Harold8dc1fd02017-04-04 19:37:48 -070088 @GuardedBy("this")
89 private final ManagedResourceArray<SpiRecord> mSpiRecords = new ManagedResourceArray<>();
Nathan Harold1afbef42017-03-01 18:55:06 -080090
Nathan Harold8dc1fd02017-04-04 19:37:48 -070091 @GuardedBy("this")
92 private final ManagedResourceArray<TransformRecord> mTransformRecords =
93 new ManagedResourceArray<>();
Nathan Harold93962f32017-03-07 13:23:36 -080094
Nathan Harold8dc1fd02017-04-04 19:37:48 -070095 @GuardedBy("this")
96 private final ManagedResourceArray<UdpSocketRecord> mUdpSocketRecords =
97 new ManagedResourceArray<>();
98
99 /**
100 * The ManagedResource class provides a facility to cleanly and reliably release system
101 * resources. It relies on two things: an IBinder that allows ManagedResource to automatically
102 * clean up in the event that the Binder dies and a user-provided resourceId that should
103 * uniquely identify the managed resource. To use this class, the user should implement the
104 * releaseResources() method that is responsible for releasing system resources when invoked.
105 */
Nathan Harold93962f32017-03-07 13:23:36 -0800106 private abstract class ManagedResource implements IBinder.DeathRecipient {
107 final int pid;
108 final int uid;
109 private IBinder mBinder;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700110 protected int mResourceId;
Nathan Harold93962f32017-03-07 13:23:36 -0800111
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700112 private AtomicInteger mReferenceCount = new AtomicInteger(0);
113
114 ManagedResource(int resourceId, IBinder binder) {
Nathan Harold93962f32017-03-07 13:23:36 -0800115 super();
116 mBinder = binder;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700117 mResourceId = resourceId;
Nathan Harold93962f32017-03-07 13:23:36 -0800118 pid = Binder.getCallingPid();
119 uid = Binder.getCallingUid();
120
121 try {
122 mBinder.linkToDeath(this, 0);
123 } catch (RemoteException e) {
124 binderDied();
125 }
126 }
127
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700128 public void addReference() {
129 mReferenceCount.incrementAndGet();
130 }
131
132 public void removeReference() {
133 if (mReferenceCount.decrementAndGet() < 0) {
134 Log.wtf(TAG, "Programming error: negative reference count");
135 }
136 }
137
138 public boolean isReferenced() {
139 return (mReferenceCount.get() > 0);
140 }
141
142 public void checkOwnerOrSystemAndThrow() {
143 if (uid != Binder.getCallingUid()
144 && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
145 throw new SecurityException("Only the owner may access managed resources!");
146 }
147 }
148
Nathan Harold93962f32017-03-07 13:23:36 -0800149 /**
150 * When this record is no longer needed for managing system resources this function should
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700151 * clean up all system resources and nullify the record. This function shall perform all
152 * necessary cleanup of the resources managed by this record.
153 *
154 * <p>NOTE: this function verifies ownership before allowing resources to be freed.
Nathan Harold93962f32017-03-07 13:23:36 -0800155 */
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700156 public final void release() throws RemoteException {
157 synchronized (IpSecService.this) {
158 if (isReferenced()) {
159 throw new IllegalStateException(
160 "Cannot release a resource that has active references!");
161 }
Nathan Harold93962f32017-03-07 13:23:36 -0800162
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700163 if (mResourceId == INVALID_RESOURCE_ID) {
164 return;
165 }
166
167 releaseResources();
168 if (mBinder != null) {
169 mBinder.unlinkToDeath(this, 0);
170 }
171 mBinder = null;
172
173 mResourceId = INVALID_RESOURCE_ID;
Nathan Harold93962f32017-03-07 13:23:36 -0800174 }
Nathan Harold93962f32017-03-07 13:23:36 -0800175 }
176
177 /**
178 * If the Binder object dies, this function is called to free the system resources that are
179 * being managed by this record and to subsequently release this record for garbage
180 * collection
181 */
182 public final void binderDied() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700183 try {
184 release();
185 } catch (Exception e) {
186 Log.e(TAG, "Failed to release resource: " + e);
187 }
Nathan Harold93962f32017-03-07 13:23:36 -0800188 }
189
190 /**
Nathan Harold93962f32017-03-07 13:23:36 -0800191 * Implement this method to release all system resources that are being protected by this
192 * record. Once the resources are released, the record should be invalidated and no longer
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700193 * used by calling release(). This should NEVER be called directly.
194 *
195 * <p>Calls to this are always guarded by IpSecService#this
Nathan Harold93962f32017-03-07 13:23:36 -0800196 */
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700197 protected abstract void releaseResources() throws RemoteException;
Nathan Harold93962f32017-03-07 13:23:36 -0800198 };
199
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700200 /**
201 * Minimal wrapper around SparseArray that performs ownership
202 * validation on element accesses.
203 */
204 private class ManagedResourceArray<T extends ManagedResource> {
205 SparseArray<T> mArray = new SparseArray<>();
Nathan Harold93962f32017-03-07 13:23:36 -0800206
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700207 T get(int key) {
208 T val = mArray.get(key);
209 val.checkOwnerOrSystemAndThrow();
210 return val;
211 }
212
213 void put(int key, T obj) {
214 checkNotNull(obj, "Null resources cannot be added");
215 mArray.put(key, obj);
216 }
217
218 void remove(int key) {
219 mArray.remove(key);
220 }
221 }
222
223 private final class TransformRecord extends ManagedResource {
224 private final IpSecConfig mConfig;
225 private final SpiRecord[] mSpis;
226 private final UdpSocketRecord mSocket;
227
228 TransformRecord(
229 int resourceId,
230 IBinder binder,
231 IpSecConfig config,
232 SpiRecord[] spis,
233 UdpSocketRecord socket) {
234 super(resourceId, binder);
Nathan Harold93962f32017-03-07 13:23:36 -0800235 mConfig = config;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700236 mSpis = spis;
237 mSocket = socket;
238
239 for (int direction : DIRECTIONS) {
240 mSpis[direction].addReference();
241 mSpis[direction].setOwnedByTransform();
242 }
243
244 if (mSocket != null) {
245 mSocket.addReference();
246 }
Nathan Harold93962f32017-03-07 13:23:36 -0800247 }
248
249 public IpSecConfig getConfig() {
250 return mConfig;
251 }
252
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700253 public SpiRecord getSpiRecord(int direction) {
254 return mSpis[direction];
255 }
256
257 /** always guarded by IpSecService#this */
Nathan Harold93962f32017-03-07 13:23:36 -0800258 @Override
259 protected void releaseResources() {
260 for (int direction : DIRECTIONS) {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700261 int spi = mSpis[direction].getSpi();
Nathan Harold93962f32017-03-07 13:23:36 -0800262 try {
263 getNetdInstance()
264 .ipSecDeleteSecurityAssociation(
265 mResourceId,
266 direction,
267 (mConfig.getLocalAddress() != null)
268 ? mConfig.getLocalAddress().getHostAddress()
269 : "",
270 (mConfig.getRemoteAddress() != null)
271 ? mConfig.getRemoteAddress().getHostAddress()
272 : "",
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700273 spi);
Nathan Harold93962f32017-03-07 13:23:36 -0800274 } catch (ServiceSpecificException e) {
275 // FIXME: get the error code and throw is at an IOException from Errno Exception
276 } catch (RemoteException e) {
277 Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
278 }
279 }
Nathan Harold93962f32017-03-07 13:23:36 -0800280
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700281 for (int direction : DIRECTIONS) {
282 mSpis[direction].removeReference();
283 }
284
285 if (mSocket != null) {
286 mSocket.removeReference();
287 }
Nathan Harold93962f32017-03-07 13:23:36 -0800288 }
289 }
290
291 private final class SpiRecord extends ManagedResource {
292 private final int mDirection;
293 private final String mLocalAddress;
294 private final String mRemoteAddress;
Nathan Harold93962f32017-03-07 13:23:36 -0800295 private int mSpi;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700296
297 private boolean mOwnedByTransform = false;
Nathan Harold93962f32017-03-07 13:23:36 -0800298
299 SpiRecord(
300 int resourceId,
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700301 IBinder binder,
Nathan Harold93962f32017-03-07 13:23:36 -0800302 int direction,
303 String localAddress,
304 String remoteAddress,
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700305 int spi) {
306 super(resourceId, binder);
Nathan Harold93962f32017-03-07 13:23:36 -0800307 mDirection = direction;
308 mLocalAddress = localAddress;
309 mRemoteAddress = remoteAddress;
310 mSpi = spi;
Nathan Harold93962f32017-03-07 13:23:36 -0800311 }
312
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700313 /** always guarded by IpSecService#this */
314 @Override
Nathan Harold93962f32017-03-07 13:23:36 -0800315 protected void releaseResources() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700316 if (mOwnedByTransform) {
317 Log.d(TAG, "Cannot release Spi " + mSpi + ": Currently locked by a Transform");
318 // Because SPIs are "handed off" to transform, objects, they should never be
319 // freed from the SpiRecord once used in a transform. (They refer to the same SA,
320 // thus ownership and responsibility for freeing these resources passes to the
321 // Transform object). Thus, we should let the user free them without penalty once
322 // they are applied in a Transform object.
323 return;
324 }
325
Nathan Harold93962f32017-03-07 13:23:36 -0800326 try {
327 getNetdInstance()
328 .ipSecDeleteSecurityAssociation(
329 mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi);
330 } catch (ServiceSpecificException e) {
331 // FIXME: get the error code and throw is at an IOException from Errno Exception
332 } catch (RemoteException e) {
333 Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId);
334 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700335
336 mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
Nathan Harold93962f32017-03-07 13:23:36 -0800337 }
338
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700339 public int getSpi() {
340 return mSpi;
341 }
342
343 public void setOwnedByTransform() {
344 if (mOwnedByTransform) {
345 // Programming error
346 new IllegalStateException("Cannot own an SPI twice!");
347 }
348
349 mOwnedByTransform = true;
Nathan Harold93962f32017-03-07 13:23:36 -0800350 }
351 }
352
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700353 private final class UdpSocketRecord extends ManagedResource {
354 private FileDescriptor mSocket;
355 private final int mPort;
Nathan Harold93962f32017-03-07 13:23:36 -0800356
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700357 UdpSocketRecord(int resourceId, IBinder binder, FileDescriptor socket, int port) {
358 super(resourceId, binder);
359 mSocket = socket;
360 mPort = port;
361 }
362
363 /** always guarded by IpSecService#this */
364 @Override
365 protected void releaseResources() {
366 Log.d(TAG, "Closing port " + mPort);
367 IoUtils.closeQuietly(mSocket);
368 mSocket = null;
369 }
370
371 public int getPort() {
372 return mPort;
373 }
374
375 public FileDescriptor getSocket() {
376 return mSocket;
377 }
378 }
Nathan Harold93962f32017-03-07 13:23:36 -0800379
Nathan Harold1afbef42017-03-01 18:55:06 -0800380 /**
381 * Constructs a new IpSecService instance
382 *
383 * @param context Binder context for this service
384 */
385 private IpSecService(Context context) {
386 mContext = context;
387 }
388
389 static IpSecService create(Context context) throws InterruptedException {
390 final IpSecService service = new IpSecService(context);
391 service.connectNativeNetdService();
392 return service;
393 }
394
395 public void systemReady() {
396 if (isNetdAlive()) {
397 Slog.d(TAG, "IpSecService is ready");
398 } else {
399 Slog.wtf(TAG, "IpSecService not ready: failed to connect to NetD Native Service!");
400 }
401 }
402
403 private void connectNativeNetdService() {
404 // Avoid blocking the system server to do this
Nathan Haroldb0e05082017-07-17 14:01:53 -0700405 new Thread() {
406 @Override
407 public void run() {
408 synchronized (IpSecService.this) {
409 NetdService.get(NETD_FETCH_TIMEOUT);
410 }
411 }
412 }.start();
Nathan Harold1afbef42017-03-01 18:55:06 -0800413 }
414
Nathan Harold93962f32017-03-07 13:23:36 -0800415 INetd getNetdInstance() throws RemoteException {
Nathan Harold1afbef42017-03-01 18:55:06 -0800416 final INetd netd = NetdService.getInstance();
417 if (netd == null) {
Nathan Harold93962f32017-03-07 13:23:36 -0800418 throw new RemoteException("Failed to Get Netd Instance");
Nathan Harold1afbef42017-03-01 18:55:06 -0800419 }
420 return netd;
421 }
422
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700423 synchronized boolean isNetdAlive() {
424 try {
425 final INetd netd = getNetdInstance();
426 if (netd == null) {
Nathan Harold1afbef42017-03-01 18:55:06 -0800427 return false;
428 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700429 return netd.isAlive();
430 } catch (RemoteException re) {
431 return false;
Nathan Harold1afbef42017-03-01 18:55:06 -0800432 }
433 }
434
435 @Override
Nathan Harold93962f32017-03-07 13:23:36 -0800436 /** Get a new SPI and maintain the reservation in the system server */
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700437 public synchronized IpSecSpiResponse reserveSecurityParameterIndex(
Nathan Harold93962f32017-03-07 13:23:36 -0800438 int direction, String remoteAddress, int requestedSpi, IBinder binder)
439 throws RemoteException {
440 int resourceId = mNextResourceId.getAndIncrement();
441
442 int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
443 String localAddress = "";
Nathan Harold93962f32017-03-07 13:23:36 -0800444 try {
445 spi =
446 getNetdInstance()
447 .ipSecAllocateSpi(
448 resourceId,
449 direction,
450 localAddress,
451 remoteAddress,
452 requestedSpi);
453 Log.d(TAG, "Allocated SPI " + spi);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700454 mSpiRecords.put(
455 resourceId,
456 new SpiRecord(resourceId, binder, direction, localAddress, remoteAddress, spi));
Nathan Harold93962f32017-03-07 13:23:36 -0800457 } catch (ServiceSpecificException e) {
458 // TODO: Add appropriate checks when other ServiceSpecificException types are supported
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700459 return new IpSecSpiResponse(
460 IpSecManager.Status.SPI_UNAVAILABLE, IpSecManager.INVALID_RESOURCE_ID, spi);
Nathan Harold93962f32017-03-07 13:23:36 -0800461 } catch (RemoteException e) {
462 throw e.rethrowFromSystemServer();
463 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700464 return new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, spi);
465 }
466
467 /* This method should only be called from Binder threads. Do not call this from
468 * within the system server as it will crash the system on failure.
469 */
470 private synchronized <T extends ManagedResource> void releaseManagedResource(
471 ManagedResourceArray<T> resArray, int resourceId, String typeName)
472 throws RemoteException {
473 // We want to non-destructively get so that we can check credentials before removing
474 // this from the records.
475 T record = resArray.get(resourceId);
476
477 if (record == null) {
478 throw new IllegalArgumentException(
479 typeName + " " + resourceId + " is not available to be deleted");
480 }
481
482 record.release();
483 resArray.remove(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800484 }
485
486 /** Release a previously allocated SPI that has been registered with the system server */
487 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700488 public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
489 releaseManagedResource(mSpiRecords, resourceId, "SecurityParameterIndex");
490 }
491
492 /**
493 * This function finds and forcibly binds to a random system port, ensuring that the port cannot
494 * be unbound.
495 *
496 * <p>A socket cannot be un-bound from a port if it was bound to that port by number. To select
497 * a random open port and then bind by number, this function creates a temp socket, binds to a
498 * random port (specifying 0), gets that port number, and then uses is to bind the user's UDP
499 * Encapsulation Socket forcibly, so that it cannot be un-bound by the user with the returned
500 * FileHandle.
501 *
502 * <p>The loop in this function handles the inherent race window between un-binding to a port
503 * and re-binding, during which the system could *technically* hand that port out to someone
504 * else.
505 */
506 private void bindToRandomPort(FileDescriptor sockFd) throws IOException {
507 for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
508 try {
509 FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
510 Os.bind(probeSocket, INADDR_ANY, 0);
511 int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
512 Os.close(probeSocket);
513 Log.v(TAG, "Binding to port " + port);
514 Os.bind(sockFd, INADDR_ANY, port);
515 return;
516 } catch (ErrnoException e) {
517 // Someone miraculously claimed the port just after we closed probeSocket.
518 if (e.errno == OsConstants.EADDRINUSE) {
519 continue;
520 }
521 throw e.rethrowAsIOException();
522 }
523 }
524 throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
525 }
Nathan Harold93962f32017-03-07 13:23:36 -0800526
527 /**
528 * Open a socket via the system server and bind it to the specified port (random if port=0).
529 * This will return a PFD to the user that represent a bound UDP socket. The system server will
530 * cache the socket and a record of its owner so that it can and must be freed when no longer
531 * needed.
532 */
533 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700534 public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
535 throws RemoteException {
536 if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) {
537 throw new IllegalArgumentException(
538 "Specified port number must be a valid non-reserved UDP port");
539 }
540 int resourceId = mNextResourceId.getAndIncrement();
541 FileDescriptor sockFd = null;
542 try {
543 sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
544
545 if (port != 0) {
546 Log.v(TAG, "Binding to port " + port);
547 Os.bind(sockFd, INADDR_ANY, port);
548 } else {
549 bindToRandomPort(sockFd);
550 }
551 // This code is common to both the unspecified and specified port cases
552 Os.setsockoptInt(
553 sockFd,
554 OsConstants.IPPROTO_UDP,
555 OsConstants.UDP_ENCAP,
556 OsConstants.UDP_ENCAP_ESPINUDP);
557
558 mUdpSocketRecords.put(
559 resourceId, new UdpSocketRecord(resourceId, binder, sockFd, port));
560 return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd);
561 } catch (IOException | ErrnoException e) {
562 IoUtils.closeQuietly(sockFd);
563 }
564 // If we make it to here, then something has gone wrong and we couldn't open a socket.
565 // The only reasonable condition that would cause that is resource unavailable.
566 return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
Nathan Harold93962f32017-03-07 13:23:36 -0800567 }
568
569 /** close a socket that has been been allocated by and registered with the system server */
570 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700571 public void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {
572
573 releaseManagedResource(mUdpSocketRecords, resourceId, "UdpEncapsulationSocket");
574 }
Nathan Harold93962f32017-03-07 13:23:36 -0800575
576 /**
577 * Create a transport mode transform, which represent two security associations (one in each
578 * direction) in the kernel. The transform will be cached by the system server and must be freed
579 * when no longer needed. It is possible to free one, deleting the SA from underneath sockets
580 * that are using it, which will result in all of those sockets becoming unable to send or
581 * receive data.
582 */
583 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700584 public synchronized IpSecTransformResponse createTransportModeTransform(
585 IpSecConfig c, IBinder binder) throws RemoteException {
Nathan Harold93962f32017-03-07 13:23:36 -0800586 int resourceId = mNextResourceId.getAndIncrement();
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700587 SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
588 // TODO: Basic input validation here since it's coming over the Binder
589 int encapType, encapLocalPort = 0, encapRemotePort = 0;
590 UdpSocketRecord socketRecord = null;
591 encapType = c.getEncapType();
592 if (encapType != IpSecTransform.ENCAP_NONE) {
593 socketRecord = mUdpSocketRecords.get(c.getEncapLocalResourceId());
594 encapLocalPort = socketRecord.getPort();
595 encapRemotePort = c.getEncapRemotePort();
596 }
597
Nathan Harold93962f32017-03-07 13:23:36 -0800598 for (int direction : DIRECTIONS) {
599 IpSecAlgorithm auth = c.getAuthentication(direction);
600 IpSecAlgorithm crypt = c.getEncryption(direction);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700601
602 spis[direction] = mSpiRecords.get(c.getSpiResourceId(direction));
603 int spi = spis[direction].getSpi();
Nathan Harold93962f32017-03-07 13:23:36 -0800604 try {
605 int result =
606 getNetdInstance()
607 .ipSecAddSecurityAssociation(
608 resourceId,
609 c.getMode(),
610 direction,
611 (c.getLocalAddress() != null)
612 ? c.getLocalAddress().getHostAddress()
613 : "",
614 (c.getRemoteAddress() != null)
615 ? c.getRemoteAddress().getHostAddress()
616 : "",
617 (c.getNetwork() != null)
618 ? c.getNetwork().getNetworkHandle()
619 : 0,
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700620 spi,
Nathan Harold93962f32017-03-07 13:23:36 -0800621 (auth != null) ? auth.getName() : "",
622 (auth != null) ? auth.getKey() : null,
623 (auth != null) ? auth.getTruncationLengthBits() : 0,
624 (crypt != null) ? crypt.getName() : "",
625 (crypt != null) ? crypt.getKey() : null,
626 (crypt != null) ? crypt.getTruncationLengthBits() : 0,
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700627 encapType,
628 encapLocalPort,
629 encapRemotePort);
630 if (result != spi) {
Nathan Harold93962f32017-03-07 13:23:36 -0800631 // TODO: cleanup the first SA if creation of second SA fails
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700632 return new IpSecTransformResponse(
633 IpSecManager.Status.SPI_UNAVAILABLE, INVALID_RESOURCE_ID);
Nathan Harold93962f32017-03-07 13:23:36 -0800634 }
635 } catch (ServiceSpecificException e) {
636 // FIXME: get the error code and throw is at an IOException from Errno Exception
637 }
638 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700639 // Both SAs were created successfully, time to construct a record and lock it away
640 mTransformRecords.put(
641 resourceId, new TransformRecord(resourceId, binder, c, spis, socketRecord));
642 return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800643 }
644
645 /**
646 * Delete a transport mode transform that was previously allocated by + registered with the
647 * system server. If this is called on an inactive (or non-existent) transform, it will not
648 * return an error. It's safe to de-allocate transforms that may have already been deleted for
649 * other reasons.
650 */
651 @Override
652 public void deleteTransportModeTransform(int resourceId) throws RemoteException {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700653 releaseManagedResource(mTransformRecords, resourceId, "IpSecTransform");
Nathan Harold93962f32017-03-07 13:23:36 -0800654 }
655
656 /**
657 * Apply an active transport mode transform to a socket, which will apply the IPsec security
658 * association as a correspondent policy to the provided socket
659 */
660 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700661 public synchronized void applyTransportModeTransform(
662 ParcelFileDescriptor socket, int resourceId) throws RemoteException {
663 // Synchronize liberally here because we are using ManagedResources in this block
664 TransformRecord info;
665 // FIXME: this code should be factored out into a security check + getter
666 info = mTransformRecords.get(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800667
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700668 if (info == null) {
669 throw new IllegalArgumentException("Transform " + resourceId + " is not active");
670 }
Nathan Harold93962f32017-03-07 13:23:36 -0800671
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700672 // TODO: make this a function.
673 if (info.pid != getCallingPid() || info.uid != getCallingUid()) {
674 throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
675 }
676
677 IpSecConfig c = info.getConfig();
678 try {
679 for (int direction : DIRECTIONS) {
680 getNetdInstance()
681 .ipSecApplyTransportModeTransform(
682 socket.getFileDescriptor(),
683 resourceId,
684 direction,
685 (c.getLocalAddress() != null)
686 ? c.getLocalAddress().getHostAddress()
687 : "",
688 (c.getRemoteAddress() != null)
689 ? c.getRemoteAddress().getHostAddress()
690 : "",
691 info.getSpiRecord(direction).getSpi());
Nathan Harold93962f32017-03-07 13:23:36 -0800692 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700693 } catch (ServiceSpecificException e) {
694 // FIXME: get the error code and throw is at an IOException from Errno Exception
Nathan Harold93962f32017-03-07 13:23:36 -0800695 }
696 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700697
Nathan Harold93962f32017-03-07 13:23:36 -0800698 /**
699 * Remove a transport mode transform from a socket, applying the default (empty) policy. This
700 * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of
701 * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not
702 * used: reserved for future improved input validation.
703 */
704 @Override
705 public void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
706 throws RemoteException {
707 try {
708 getNetdInstance().ipSecRemoveTransportModeTransform(socket.getFileDescriptor());
709 } catch (ServiceSpecificException e) {
710 // FIXME: get the error code and throw is at an IOException from Errno Exception
711 }
712 }
713
714 @Override
Nathan Harold1afbef42017-03-01 18:55:06 -0800715 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
716 mContext.enforceCallingOrSelfPermission(DUMP, TAG);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700717 // TODO: Add dump code to print out a log of all the resources being tracked
Nathan Harold1afbef42017-03-01 18:55:06 -0800718 pw.println("IpSecService Log:");
719 pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
720 pw.println();
721 }
722}