blob: 2116d9a818cfb28659f18d655c166081107e7235 [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 Harolda10003d2017-08-23 13:46:33 -070036import android.net.NetworkUtils;
Nathan Harold1afbef42017-03-01 18:55:06 -080037import android.net.util.NetdService;
Nathan Harold93962f32017-03-07 13:23:36 -080038import android.os.Binder;
Nathan Harold93962f32017-03-07 13:23:36 -080039import android.os.IBinder;
40import android.os.ParcelFileDescriptor;
Nathan Harold1afbef42017-03-01 18:55:06 -080041import android.os.RemoteException;
Nathan Harold93962f32017-03-07 13:23:36 -080042import android.os.ServiceSpecificException;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070043import android.system.ErrnoException;
44import android.system.Os;
45import android.system.OsConstants;
Nathan Harolda10003d2017-08-23 13:46:33 -070046import android.text.TextUtils;
Nathan Harold1afbef42017-03-01 18:55:06 -080047import android.util.Log;
48import android.util.Slog;
Nathan Harold93962f32017-03-07 13:23:36 -080049import android.util.SparseArray;
Nathan Harolda10003d2017-08-23 13:46:33 -070050
Nathan Harold93962f32017-03-07 13:23:36 -080051import com.android.internal.annotations.GuardedBy;
ludi1a06aa72017-05-12 09:15:00 -070052import com.android.internal.annotations.VisibleForTesting;
Nathan Harolda10003d2017-08-23 13:46:33 -070053
Nathan Harold1afbef42017-03-01 18:55:06 -080054import java.io.FileDescriptor;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070055import java.io.IOException;
Nathan Harold1afbef42017-03-01 18:55:06 -080056import java.io.PrintWriter;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070057import java.net.InetAddress;
58import java.net.InetSocketAddress;
59import java.net.UnknownHostException;
Benedict Wong409c8ca2017-10-26 19:41:43 -070060import java.util.ArrayList;
61import java.util.List;
Nathan Harold93962f32017-03-07 13:23:36 -080062import java.util.concurrent.atomic.AtomicInteger;
Nathan Harolda10003d2017-08-23 13:46:33 -070063
Nathan Harold8dc1fd02017-04-04 19:37:48 -070064import libcore.io.IoUtils;
Nathan Harold1afbef42017-03-01 18:55:06 -080065
Benedict Wong409c8ca2017-10-26 19:41:43 -070066/**
67 * A service to manage multiple clients that want to access the IpSec API. The service is
68 * responsible for maintaining a list of clients and managing the resources (and related quotas)
69 * that each of them own.
70 *
71 * <p>Synchronization in IpSecService is done on all entrypoints due to potential race conditions at
72 * the kernel/xfrm level. Further, this allows the simplifying assumption to be made that only one
73 * thread is ever running at a time.
74 *
75 * @hide
76 */
Nathan Harold1afbef42017-03-01 18:55:06 -080077public class IpSecService extends IIpSecService.Stub {
78 private static final String TAG = "IpSecService";
79 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
Nathan Harold8dc1fd02017-04-04 19:37:48 -070080
Nathan Harold1afbef42017-03-01 18:55:06 -080081 private static final String NETD_SERVICE_NAME = "netd";
Nathan Harold93962f32017-03-07 13:23:36 -080082 private static final int[] DIRECTIONS =
83 new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN};
Nathan Harold1afbef42017-03-01 18:55:06 -080084
ludi1a06aa72017-05-12 09:15:00 -070085 private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms
Nathan Harold8dc1fd02017-04-04 19:37:48 -070086 private static final int MAX_PORT_BIND_ATTEMPTS = 10;
87 private static final InetAddress INADDR_ANY;
88
89 static {
90 try {
91 INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
92 } catch (UnknownHostException e) {
93 throw new RuntimeException(e);
94 }
95 }
96
97 static final int FREE_PORT_MIN = 1024; // ports 1-1023 are reserved
98 static final int PORT_MAX = 0xFFFF; // ports are an unsigned 16-bit integer
99
100 /* Binder context for this service */
Nathan Harold1afbef42017-03-01 18:55:06 -0800101 private final Context mContext;
102
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700103 /** Should be a never-repeating global ID for resources */
104 private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
Nathan Harold1afbef42017-03-01 18:55:06 -0800105
ludi1a06aa72017-05-12 09:15:00 -0700106 interface IpSecServiceConfiguration {
107 INetd getNetdInstance() throws RemoteException;
108
109 static IpSecServiceConfiguration GETSRVINSTANCE =
110 new IpSecServiceConfiguration() {
111 @Override
112 public INetd getNetdInstance() throws RemoteException {
113 final INetd netd = NetdService.getInstance();
114 if (netd == null) {
115 throw new RemoteException("Failed to Get Netd Instance");
116 }
117 return netd;
118 }
119 };
120 }
121
122 private final IpSecServiceConfiguration mSrvConfig;
123
Benedict Wong409c8ca2017-10-26 19:41:43 -0700124 /**
125 * Interface for user-reference and kernel-resource cleanup.
126 *
127 * <p>This interface must be implemented for a resource to be reference counted.
128 */
129 @VisibleForTesting
130 public interface IResource {
131 /**
132 * Invalidates a IResource object, ensuring it is invalid for the purposes of allocating new
133 * objects dependent on it.
134 *
135 * <p>Implementations of this method are expected to remove references to the IResource
136 * object from the IpSecService's tracking arrays. The removal from the arrays ensures that
137 * the resource is considered invalid for user access or allocation or use in other
138 * resources.
139 *
140 * <p>References to the IResource object may be held by other RefcountedResource objects,
141 * and as such, the kernel resources and quota may not be cleaned up.
142 */
143 void invalidate() throws RemoteException;
144
145 /**
146 * Releases underlying resources and related quotas.
147 *
148 * <p>Implementations of this method are expected to remove all system resources that are
149 * tracked by the IResource object. Due to other RefcountedResource objects potentially
Benedict Wong344bd622017-11-16 15:27:22 -0800150 * having references to the IResource object, freeUnderlyingResources may not always be
Benedict Wong409c8ca2017-10-26 19:41:43 -0700151 * called from releaseIfUnreferencedRecursively().
152 */
153 void freeUnderlyingResources() throws RemoteException;
154 }
155
156 /**
157 * RefcountedResource manages references and dependencies in an exclusively acyclic graph.
158 *
159 * <p>RefcountedResource implements both explicit and implicit resource management. Creating a
160 * RefcountedResource object creates an explicit reference that must be freed by calling
161 * userRelease(). Additionally, adding this object as a child of another RefcountedResource
162 * object will add an implicit reference.
163 *
164 * <p>Resources are cleaned up when all references, both implicit and explicit, are released
165 * (ie, when userRelease() is called and when all parents have called releaseReference() on this
166 * object.)
167 */
168 @VisibleForTesting
169 public class RefcountedResource<T extends IResource> implements IBinder.DeathRecipient {
170 private final T mResource;
171 private final List<RefcountedResource> mChildren;
172 int mRefCount = 1; // starts at 1 for user's reference.
173 IBinder mBinder;
174
175 RefcountedResource(T resource, IBinder binder, RefcountedResource... children) {
176 synchronized (IpSecService.this) {
177 this.mResource = resource;
178 this.mChildren = new ArrayList<>(children.length);
179 this.mBinder = binder;
180
181 for (RefcountedResource child : children) {
182 mChildren.add(child);
183 child.mRefCount++;
184 }
185
186 try {
187 mBinder.linkToDeath(this, 0);
188 } catch (RemoteException e) {
189 binderDied();
190 }
191 }
192 }
193
194 /**
195 * If the Binder object dies, this function is called to free the system resources that are
Benedict Wong344bd622017-11-16 15:27:22 -0800196 * being tracked by this record and to subsequently release this record for garbage
Benedict Wong409c8ca2017-10-26 19:41:43 -0700197 * collection
198 */
199 @Override
200 public void binderDied() {
201 synchronized (IpSecService.this) {
202 try {
203 userRelease();
204 } catch (Exception e) {
205 Log.e(TAG, "Failed to release resource: " + e);
206 }
207 }
208 }
209
210 public T getResource() {
211 return mResource;
212 }
213
214 /**
215 * Unlinks from binder and performs IpSecService resource cleanup (removes from resource
216 * arrays)
217 *
218 * <p>If this method has been previously called, the RefcountedResource's binder field will
219 * be null, and the method will return without performing the cleanup a second time.
220 *
221 * <p>Note that calling this function does not imply that kernel resources will be freed at
222 * this time, or that the related quota will be returned. Such actions will only be
223 * performed upon the reference count reaching zero.
224 */
225 @GuardedBy("IpSecService.this")
226 public void userRelease() throws RemoteException {
227 // Prevent users from putting reference counts into a bad state by calling
228 // userRelease() multiple times.
229 if (mBinder == null) {
230 return;
231 }
232
233 mBinder.unlinkToDeath(this, 0);
234 mBinder = null;
235
236 mResource.invalidate();
237
238 releaseReference();
239 }
240
241 /**
242 * Removes a reference to this resource. If the resultant reference count is zero, the
243 * underlying resources are freed, and references to all child resources are also dropped
244 * recursively (resulting in them freeing their resources and children, etcetera)
245 *
246 * <p>This method also sets the reference count to an invalid value (-1) to signify that it
247 * has been fully released. Any subsequent calls to this method will result in an
248 * IllegalStateException being thrown due to resource already having been previously
249 * released
250 */
251 @VisibleForTesting
252 @GuardedBy("IpSecService.this")
253 public void releaseReference() throws RemoteException {
254 mRefCount--;
255
256 if (mRefCount > 0) {
257 return;
258 } else if (mRefCount < 0) {
259 throw new IllegalStateException(
260 "Invalid operation - resource has already been released.");
261 }
262
263 // Cleanup own resources
264 mResource.freeUnderlyingResources();
265
266 // Cleanup child resources as needed
267 for (RefcountedResource<? extends IResource> child : mChildren) {
268 child.releaseReference();
269 }
270
271 // Enforce that resource cleanup can only be called once
272 // By decrementing the refcount (from 0 to -1), the next call will throw an
273 // IllegalStateException - it has already been released fully.
274 mRefCount--;
275 }
276
277 @Override
278 public String toString() {
279 return new StringBuilder()
280 .append("{mResource=")
281 .append(mResource)
282 .append(", mRefCount=")
283 .append(mRefCount)
284 .append(", mChildren=")
285 .append(mChildren)
286 .append("}")
287 .toString();
288 }
289 }
290
Nathan Harolda1afbd82017-04-24 16:16:34 -0700291 /* Very simple counting class that looks much like a counting semaphore */
Benedict Wong344bd622017-11-16 15:27:22 -0800292 @VisibleForTesting
293 static class ResourceTracker {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700294 private final int mMax;
295 int mCurrent;
296
297 ResourceTracker(int max) {
298 mMax = max;
299 mCurrent = 0;
300 }
301
Benedict Wong344bd622017-11-16 15:27:22 -0800302 boolean isAvailable() {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700303 return (mCurrent < mMax);
304 }
305
Benedict Wong344bd622017-11-16 15:27:22 -0800306 void take() {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700307 if (!isAvailable()) {
308 Log.wtf(TAG, "Too many resources allocated!");
309 }
310 mCurrent++;
311 }
312
Benedict Wong344bd622017-11-16 15:27:22 -0800313 void give() {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700314 if (mCurrent <= 0) {
315 Log.wtf(TAG, "We've released this resource too many times");
316 }
317 mCurrent--;
318 }
ludi3e5ea232017-08-10 15:44:40 -0700319
320 @Override
321 public String toString() {
322 return new StringBuilder()
323 .append("{mCurrent=")
324 .append(mCurrent)
325 .append(", mMax=")
326 .append(mMax)
327 .append("}")
328 .toString();
329 }
Nathan Harolda1afbd82017-04-24 16:16:34 -0700330 }
331
Benedict Wong344bd622017-11-16 15:27:22 -0800332 @VisibleForTesting
333 static final class UserRecord {
334 /* Type names */
335 public static final String TYPENAME_SPI = "SecurityParameterIndex";
336 public static final String TYPENAME_TRANSFORM = "IpSecTransform";
337 public static final String TYPENAME_ENCAP_SOCKET = "UdpEncapSocket";
338
339 /* Maximum number of each type of resource that a single UID may possess */
Nathan Harolda1afbd82017-04-24 16:16:34 -0700340 public static final int MAX_NUM_ENCAP_SOCKETS = 2;
Nathan Harolda1afbd82017-04-24 16:16:34 -0700341 public static final int MAX_NUM_TRANSFORMS = 4;
Nathan Harolda1afbd82017-04-24 16:16:34 -0700342 public static final int MAX_NUM_SPIS = 8;
343
Benedict Wong344bd622017-11-16 15:27:22 -0800344 final RefcountedResourceArray<SpiRecord> mSpiRecords =
345 new RefcountedResourceArray<>(TYPENAME_SPI);
346 final ResourceTracker mSpiQuotaTracker = new ResourceTracker(MAX_NUM_SPIS);
ludi3e5ea232017-08-10 15:44:40 -0700347
Benedict Wong344bd622017-11-16 15:27:22 -0800348 final RefcountedResourceArray<TransformRecord> mTransformRecords =
349 new RefcountedResourceArray<>(TYPENAME_TRANSFORM);
350 final ResourceTracker mTransformQuotaTracker = new ResourceTracker(MAX_NUM_TRANSFORMS);
351
352 final RefcountedResourceArray<EncapSocketRecord> mEncapSocketRecords =
353 new RefcountedResourceArray<>(TYPENAME_ENCAP_SOCKET);
354 final ResourceTracker mSocketQuotaTracker = new ResourceTracker(MAX_NUM_ENCAP_SOCKETS);
355
356 void removeSpiRecord(int resourceId) {
357 mSpiRecords.remove(resourceId);
Nathan Harolda1afbd82017-04-24 16:16:34 -0700358 }
359
Benedict Wong344bd622017-11-16 15:27:22 -0800360 void removeTransformRecord(int resourceId) {
361 mTransformRecords.remove(resourceId);
362 }
363
364 void removeEncapSocketRecord(int resourceId) {
365 mEncapSocketRecords.remove(resourceId);
366 }
367
368 @Override
369 public String toString() {
370 return new StringBuilder()
371 .append("{mSpiQuotaTracker=")
372 .append(mSpiQuotaTracker)
373 .append(", mTransformQuotaTracker=")
374 .append(mTransformQuotaTracker)
375 .append(", mSocketQuotaTracker=")
376 .append(mSocketQuotaTracker)
377 .append(", mSpiRecords=")
378 .append(mSpiRecords)
379 .append(", mTransformRecords=")
380 .append(mTransformRecords)
381 .append(", mEncapSocketRecords=")
382 .append(mEncapSocketRecords)
383 .append("}")
384 .toString();
385 }
386 }
387
388 @VisibleForTesting
389 static final class UserResourceTracker {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700390 private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
391
Benedict Wong344bd622017-11-16 15:27:22 -0800392 /** Never-fail getter that populates the list of UIDs as-needed */
393 public UserRecord getUserRecord(int uid) {
394 checkCallerUid(uid);
395
Nathan Harolda1afbd82017-04-24 16:16:34 -0700396 UserRecord r = mUserRecords.get(uid);
397 if (r == null) {
398 r = new UserRecord();
399 mUserRecords.put(uid, r);
400 }
401 return r;
402 }
ludi3e5ea232017-08-10 15:44:40 -0700403
Benedict Wong344bd622017-11-16 15:27:22 -0800404 /** Safety method; guards against access of other user's UserRecords */
405 private void checkCallerUid(int uid) {
406 if (uid != Binder.getCallingUid()
407 && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
408 throw new SecurityException("Attempted access of unowned resources");
409 }
410 }
411
ludi3e5ea232017-08-10 15:44:40 -0700412 @Override
413 public String toString() {
414 return mUserRecords.toString();
415 }
Nathan Harolda1afbd82017-04-24 16:16:34 -0700416 }
417
Benedict Wong344bd622017-11-16 15:27:22 -0800418 @VisibleForTesting final UserResourceTracker mUserResourceTracker = new UserResourceTracker();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700419
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700420 /**
Benedict Wong344bd622017-11-16 15:27:22 -0800421 * The KernelResourceRecord class provides a facility to cleanly and reliably track system
422 * resources. It relies on a provided resourceId that should uniquely identify the kernel
423 * resource. To use this class, the user should implement the invalidate() and
424 * freeUnderlyingResources() methods that are responsible for cleaning up IpSecService resource
425 * tracking arrays and kernel resources, respectively
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700426 */
Benedict Wong344bd622017-11-16 15:27:22 -0800427 private abstract class KernelResourceRecord implements IResource {
Nathan Harold93962f32017-03-07 13:23:36 -0800428 final int pid;
429 final int uid;
Benedict Wong344bd622017-11-16 15:27:22 -0800430 protected final int mResourceId;
Nathan Harold93962f32017-03-07 13:23:36 -0800431
Benedict Wong344bd622017-11-16 15:27:22 -0800432 KernelResourceRecord(int resourceId) {
Nathan Harold93962f32017-03-07 13:23:36 -0800433 super();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700434 if (resourceId == INVALID_RESOURCE_ID) {
435 throw new IllegalArgumentException("Resource ID must not be INVALID_RESOURCE_ID");
436 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700437 mResourceId = resourceId;
Nathan Harold93962f32017-03-07 13:23:36 -0800438 pid = Binder.getCallingPid();
439 uid = Binder.getCallingUid();
440
Nathan Harolda1afbd82017-04-24 16:16:34 -0700441 getResourceTracker().take();
Nathan Harold93962f32017-03-07 13:23:36 -0800442 }
443
Benedict Wong344bd622017-11-16 15:27:22 -0800444 @Override
445 public abstract void invalidate() throws RemoteException;
446
447 /** Convenience method; retrieves the user resource record for the stored UID. */
448 protected UserRecord getUserRecord() {
449 return mUserResourceTracker.getUserRecord(uid);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700450 }
451
Benedict Wong344bd622017-11-16 15:27:22 -0800452 @Override
453 public abstract void freeUnderlyingResources() throws RemoteException;
ludib0c95b12017-05-22 10:52:23 -0700454
Nathan Harolda1afbd82017-04-24 16:16:34 -0700455 /** Get the resource tracker for this resource */
456 protected abstract ResourceTracker getResourceTracker();
457
ludib0c95b12017-05-22 10:52:23 -0700458 @Override
459 public String toString() {
460 return new StringBuilder()
461 .append("{mResourceId=")
462 .append(mResourceId)
463 .append(", pid=")
464 .append(pid)
465 .append(", uid=")
466 .append(uid)
ludib0c95b12017-05-22 10:52:23 -0700467 .append("}")
468 .toString();
469 }
Nathan Harold93962f32017-03-07 13:23:36 -0800470 };
471
Benedict Wong344bd622017-11-16 15:27:22 -0800472 // TODO: Move this to right after RefcountedResource. With this here, Gerrit was showing many
473 // more things as changed.
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700474 /**
Benedict Wong344bd622017-11-16 15:27:22 -0800475 * Thin wrapper over SparseArray to ensure resources exist, and simplify generic typing.
476 *
477 * <p>RefcountedResourceArray prevents null insertions, and throws an IllegalArgumentException
478 * if a key is not found during a retrieval process.
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700479 */
Benedict Wong344bd622017-11-16 15:27:22 -0800480 static class RefcountedResourceArray<T extends IResource> {
481 SparseArray<RefcountedResource<T>> mArray = new SparseArray<>();
482 private final String mTypeName;
Nathan Harold93962f32017-03-07 13:23:36 -0800483
Benedict Wong344bd622017-11-16 15:27:22 -0800484 public RefcountedResourceArray(String typeName) {
485 this.mTypeName = typeName;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700486 }
487
Benedict Wong344bd622017-11-16 15:27:22 -0800488 /**
489 * Accessor method to get inner resource object.
490 *
491 * @throws IllegalArgumentException if no resource with provided key is found.
492 */
493 T getResourceOrThrow(int key) {
494 return getRefcountedResourceOrThrow(key).getResource();
495 }
496
497 /**
498 * Accessor method to get reference counting wrapper.
499 *
500 * @throws IllegalArgumentException if no resource with provided key is found.
501 */
502 RefcountedResource<T> getRefcountedResourceOrThrow(int key) {
503 RefcountedResource<T> resource = mArray.get(key);
504 if (resource == null) {
505 throw new IllegalArgumentException(
506 String.format("No such %s found for given id: %d", mTypeName, key));
507 }
508
509 return resource;
510 }
511
512 void put(int key, RefcountedResource<T> obj) {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700513 checkNotNull(obj, "Null resources cannot be added");
514 mArray.put(key, obj);
515 }
516
517 void remove(int key) {
518 mArray.remove(key);
519 }
ludib0c95b12017-05-22 10:52:23 -0700520
521 @Override
522 public String toString() {
523 return mArray.toString();
524 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700525 }
526
Benedict Wong344bd622017-11-16 15:27:22 -0800527 private final class TransformRecord extends KernelResourceRecord {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700528 private final IpSecConfig mConfig;
529 private final SpiRecord[] mSpis;
Benedict Wong344bd622017-11-16 15:27:22 -0800530 private final EncapSocketRecord mSocket;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700531
532 TransformRecord(
Benedict Wong344bd622017-11-16 15:27:22 -0800533 int resourceId, IpSecConfig config, SpiRecord[] spis, EncapSocketRecord socket) {
534 super(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800535 mConfig = config;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700536 mSpis = spis;
537 mSocket = socket;
Nathan Harold93962f32017-03-07 13:23:36 -0800538 }
539
540 public IpSecConfig getConfig() {
541 return mConfig;
542 }
543
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700544 public SpiRecord getSpiRecord(int direction) {
545 return mSpis[direction];
546 }
547
548 /** always guarded by IpSecService#this */
Nathan Harold93962f32017-03-07 13:23:36 -0800549 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800550 public void freeUnderlyingResources() {
Nathan Harold93962f32017-03-07 13:23:36 -0800551 for (int direction : DIRECTIONS) {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700552 int spi = mSpis[direction].getSpi();
Nathan Harold93962f32017-03-07 13:23:36 -0800553 try {
ludi1a06aa72017-05-12 09:15:00 -0700554 mSrvConfig
555 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800556 .ipSecDeleteSecurityAssociation(
557 mResourceId,
558 direction,
Nathan Harolda10003d2017-08-23 13:46:33 -0700559 mConfig.getLocalAddress(),
560 mConfig.getRemoteAddress(),
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700561 spi);
Nathan Harold93962f32017-03-07 13:23:36 -0800562 } catch (ServiceSpecificException e) {
563 // FIXME: get the error code and throw is at an IOException from Errno Exception
564 } catch (RemoteException e) {
565 Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
566 }
567 }
Nathan Harold93962f32017-03-07 13:23:36 -0800568
Benedict Wong344bd622017-11-16 15:27:22 -0800569 getResourceTracker().give();
Nathan Harold93962f32017-03-07 13:23:36 -0800570 }
ludib0c95b12017-05-22 10:52:23 -0700571
Benedict Wong344bd622017-11-16 15:27:22 -0800572 @Override
573 public void invalidate() throws RemoteException {
574 getUserRecord().removeTransformRecord(mResourceId);
575 }
576
577 @Override
Nathan Harolda1afbd82017-04-24 16:16:34 -0700578 protected ResourceTracker getResourceTracker() {
Benedict Wong344bd622017-11-16 15:27:22 -0800579 return getUserRecord().mTransformQuotaTracker;
Nathan Harolda1afbd82017-04-24 16:16:34 -0700580 }
581
ludib0c95b12017-05-22 10:52:23 -0700582 @Override
583 public String toString() {
584 StringBuilder strBuilder = new StringBuilder();
585 strBuilder
586 .append("{super=")
587 .append(super.toString())
588 .append(", mSocket=")
589 .append(mSocket)
590 .append(", mSpis[OUT].mResourceId=")
591 .append(mSpis[IpSecTransform.DIRECTION_OUT].mResourceId)
592 .append(", mSpis[IN].mResourceId=")
593 .append(mSpis[IpSecTransform.DIRECTION_IN].mResourceId)
594 .append(", mConfig=")
595 .append(mConfig)
596 .append("}");
597 return strBuilder.toString();
598 }
Nathan Harold93962f32017-03-07 13:23:36 -0800599 }
600
Benedict Wong344bd622017-11-16 15:27:22 -0800601 private final class SpiRecord extends KernelResourceRecord {
Nathan Harold93962f32017-03-07 13:23:36 -0800602 private final int mDirection;
603 private final String mLocalAddress;
604 private final String mRemoteAddress;
Nathan Harold93962f32017-03-07 13:23:36 -0800605 private int mSpi;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700606
607 private boolean mOwnedByTransform = false;
Nathan Harold93962f32017-03-07 13:23:36 -0800608
609 SpiRecord(
610 int resourceId,
611 int direction,
612 String localAddress,
613 String remoteAddress,
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700614 int spi) {
Benedict Wong344bd622017-11-16 15:27:22 -0800615 super(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800616 mDirection = direction;
617 mLocalAddress = localAddress;
618 mRemoteAddress = remoteAddress;
619 mSpi = spi;
Nathan Harold93962f32017-03-07 13:23:36 -0800620 }
621
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700622 /** always guarded by IpSecService#this */
623 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800624 public void freeUnderlyingResources() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700625 if (mOwnedByTransform) {
626 Log.d(TAG, "Cannot release Spi " + mSpi + ": Currently locked by a Transform");
627 // Because SPIs are "handed off" to transform, objects, they should never be
628 // freed from the SpiRecord once used in a transform. (They refer to the same SA,
629 // thus ownership and responsibility for freeing these resources passes to the
630 // Transform object). Thus, we should let the user free them without penalty once
631 // they are applied in a Transform object.
632 return;
633 }
634
Nathan Harold93962f32017-03-07 13:23:36 -0800635 try {
ludi1a06aa72017-05-12 09:15:00 -0700636 mSrvConfig
637 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800638 .ipSecDeleteSecurityAssociation(
639 mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi);
640 } catch (ServiceSpecificException e) {
641 // FIXME: get the error code and throw is at an IOException from Errno Exception
642 } catch (RemoteException e) {
643 Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId);
644 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700645
646 mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
Nathan Harold93962f32017-03-07 13:23:36 -0800647
Benedict Wong344bd622017-11-16 15:27:22 -0800648 getResourceTracker().give();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700649 }
650
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700651 public int getSpi() {
652 return mSpi;
653 }
654
655 public void setOwnedByTransform() {
656 if (mOwnedByTransform) {
657 // Programming error
Andreas Gamped6d8e452017-07-11 10:25:09 -0700658 throw new IllegalStateException("Cannot own an SPI twice!");
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700659 }
660
661 mOwnedByTransform = true;
Nathan Harold93962f32017-03-07 13:23:36 -0800662 }
ludib0c95b12017-05-22 10:52:23 -0700663
664 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800665 public void invalidate() throws RemoteException {
666 getUserRecord().removeSpiRecord(mResourceId);
667 }
668
669 @Override
670 protected ResourceTracker getResourceTracker() {
671 return getUserRecord().mSpiQuotaTracker;
672 }
673
674 @Override
ludib0c95b12017-05-22 10:52:23 -0700675 public String toString() {
676 StringBuilder strBuilder = new StringBuilder();
677 strBuilder
678 .append("{super=")
679 .append(super.toString())
680 .append(", mSpi=")
681 .append(mSpi)
682 .append(", mDirection=")
683 .append(mDirection)
684 .append(", mLocalAddress=")
685 .append(mLocalAddress)
686 .append(", mRemoteAddress=")
687 .append(mRemoteAddress)
688 .append(", mOwnedByTransform=")
689 .append(mOwnedByTransform)
690 .append("}");
691 return strBuilder.toString();
692 }
Nathan Harold93962f32017-03-07 13:23:36 -0800693 }
694
Benedict Wong344bd622017-11-16 15:27:22 -0800695 private final class EncapSocketRecord extends KernelResourceRecord {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700696 private FileDescriptor mSocket;
697 private final int mPort;
Nathan Harold93962f32017-03-07 13:23:36 -0800698
Benedict Wong344bd622017-11-16 15:27:22 -0800699 EncapSocketRecord(int resourceId, FileDescriptor socket, int port) {
700 super(resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700701 mSocket = socket;
702 mPort = port;
703 }
704
705 /** always guarded by IpSecService#this */
706 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800707 public void freeUnderlyingResources() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700708 Log.d(TAG, "Closing port " + mPort);
709 IoUtils.closeQuietly(mSocket);
710 mSocket = null;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700711
Benedict Wong344bd622017-11-16 15:27:22 -0800712 getResourceTracker().give();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700713 }
714
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700715 public int getPort() {
716 return mPort;
717 }
718
719 public FileDescriptor getSocket() {
720 return mSocket;
721 }
ludib0c95b12017-05-22 10:52:23 -0700722
723 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800724 protected ResourceTracker getResourceTracker() {
725 return getUserRecord().mSocketQuotaTracker;
726 }
727
728 @Override
729 public void invalidate() {
730 getUserRecord().removeEncapSocketRecord(mResourceId);
731 }
732
733 @Override
ludib0c95b12017-05-22 10:52:23 -0700734 public String toString() {
735 return new StringBuilder()
736 .append("{super=")
737 .append(super.toString())
738 .append(", mSocket=")
739 .append(mSocket)
740 .append(", mPort=")
741 .append(mPort)
742 .append("}")
743 .toString();
744 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700745 }
Nathan Harold93962f32017-03-07 13:23:36 -0800746
Nathan Harold1afbef42017-03-01 18:55:06 -0800747 /**
748 * Constructs a new IpSecService instance
749 *
750 * @param context Binder context for this service
751 */
752 private IpSecService(Context context) {
ludi1a06aa72017-05-12 09:15:00 -0700753 this(context, IpSecServiceConfiguration.GETSRVINSTANCE);
Nathan Harold1afbef42017-03-01 18:55:06 -0800754 }
755
756 static IpSecService create(Context context) throws InterruptedException {
757 final IpSecService service = new IpSecService(context);
758 service.connectNativeNetdService();
759 return service;
760 }
761
ludi1a06aa72017-05-12 09:15:00 -0700762 /** @hide */
763 @VisibleForTesting
764 public IpSecService(Context context, IpSecServiceConfiguration config) {
765 mContext = context;
766 mSrvConfig = config;
767 }
768
Nathan Harold1afbef42017-03-01 18:55:06 -0800769 public void systemReady() {
770 if (isNetdAlive()) {
771 Slog.d(TAG, "IpSecService is ready");
772 } else {
773 Slog.wtf(TAG, "IpSecService not ready: failed to connect to NetD Native Service!");
774 }
775 }
776
777 private void connectNativeNetdService() {
778 // Avoid blocking the system server to do this
Nathan Haroldb0e05082017-07-17 14:01:53 -0700779 new Thread() {
780 @Override
781 public void run() {
782 synchronized (IpSecService.this) {
ludi1a06aa72017-05-12 09:15:00 -0700783 NetdService.get(NETD_FETCH_TIMEOUT_MS);
Nathan Haroldb0e05082017-07-17 14:01:53 -0700784 }
785 }
786 }.start();
Nathan Harold1afbef42017-03-01 18:55:06 -0800787 }
788
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700789 synchronized boolean isNetdAlive() {
790 try {
ludi1a06aa72017-05-12 09:15:00 -0700791 final INetd netd = mSrvConfig.getNetdInstance();
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700792 if (netd == null) {
Nathan Harold1afbef42017-03-01 18:55:06 -0800793 return false;
794 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700795 return netd.isAlive();
796 } catch (RemoteException re) {
797 return false;
Nathan Harold1afbef42017-03-01 18:55:06 -0800798 }
799 }
800
Nathan Harolda10003d2017-08-23 13:46:33 -0700801 /**
802 * Checks that the provided InetAddress is valid for use in an IPsec SA. The address must not be
803 * a wildcard address and must be in a numeric form such as 1.2.3.4 or 2001::1.
804 */
805 private static void checkInetAddress(String inetAddress) {
806 if (TextUtils.isEmpty(inetAddress)) {
807 throw new IllegalArgumentException("Unspecified address");
808 }
809
810 InetAddress checkAddr = NetworkUtils.numericToInetAddress(inetAddress);
811
812 if (checkAddr.isAnyLocalAddress()) {
813 throw new IllegalArgumentException("Inappropriate wildcard address: " + inetAddress);
814 }
815 }
816
817 /**
818 * Checks the user-provided direction field and throws an IllegalArgumentException if it is not
819 * DIRECTION_IN or DIRECTION_OUT
820 */
821 private static void checkDirection(int direction) {
822 switch (direction) {
823 case IpSecTransform.DIRECTION_OUT:
824 case IpSecTransform.DIRECTION_IN:
825 return;
826 }
827 throw new IllegalArgumentException("Invalid Direction: " + direction);
828 }
829
Nathan Harold1afbef42017-03-01 18:55:06 -0800830 @Override
Nathan Harold93962f32017-03-07 13:23:36 -0800831 /** Get a new SPI and maintain the reservation in the system server */
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700832 public synchronized IpSecSpiResponse reserveSecurityParameterIndex(
Nathan Harold93962f32017-03-07 13:23:36 -0800833 int direction, String remoteAddress, int requestedSpi, IBinder binder)
834 throws RemoteException {
Nathan Harolda10003d2017-08-23 13:46:33 -0700835 checkDirection(direction);
836 checkInetAddress(remoteAddress);
837 /* requestedSpi can be anything in the int range, so no check is needed. */
838 checkNotNull(binder, "Null Binder passed to reserveSecurityParameterIndex");
839
Benedict Wong344bd622017-11-16 15:27:22 -0800840 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
Nathan Harold93962f32017-03-07 13:23:36 -0800841 int resourceId = mNextResourceId.getAndIncrement();
842
843 int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
844 String localAddress = "";
Nathan Harolda1afbd82017-04-24 16:16:34 -0700845
Nathan Harold93962f32017-03-07 13:23:36 -0800846 try {
Benedict Wong344bd622017-11-16 15:27:22 -0800847 if (!userRecord.mSpiQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700848 return new IpSecSpiResponse(
Nathan Harolda10003d2017-08-23 13:46:33 -0700849 IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
Nathan Harolda1afbd82017-04-24 16:16:34 -0700850 }
Nathan Harold93962f32017-03-07 13:23:36 -0800851 spi =
ludi1a06aa72017-05-12 09:15:00 -0700852 mSrvConfig
853 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800854 .ipSecAllocateSpi(
855 resourceId,
856 direction,
857 localAddress,
858 remoteAddress,
859 requestedSpi);
860 Log.d(TAG, "Allocated SPI " + spi);
Benedict Wong344bd622017-11-16 15:27:22 -0800861 userRecord.mSpiRecords.put(
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700862 resourceId,
Benedict Wong344bd622017-11-16 15:27:22 -0800863 new RefcountedResource<SpiRecord>(
864 new SpiRecord(resourceId, direction, localAddress, remoteAddress, spi),
865 binder));
Nathan Harold93962f32017-03-07 13:23:36 -0800866 } catch (ServiceSpecificException e) {
867 // TODO: Add appropriate checks when other ServiceSpecificException types are supported
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700868 return new IpSecSpiResponse(
Nathan Harolda1afbd82017-04-24 16:16:34 -0700869 IpSecManager.Status.SPI_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
Nathan Harold93962f32017-03-07 13:23:36 -0800870 } catch (RemoteException e) {
871 throw e.rethrowFromSystemServer();
872 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700873 return new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, spi);
874 }
875
876 /* This method should only be called from Binder threads. Do not call this from
877 * within the system server as it will crash the system on failure.
878 */
Benedict Wong344bd622017-11-16 15:27:22 -0800879 private void releaseResource(RefcountedResourceArray resArray, int resourceId)
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700880 throws RemoteException {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700881
Benedict Wong344bd622017-11-16 15:27:22 -0800882 resArray.getRefcountedResourceOrThrow(resourceId).userRelease();
Nathan Harold93962f32017-03-07 13:23:36 -0800883 }
884
885 /** Release a previously allocated SPI that has been registered with the system server */
886 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800887 public synchronized void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
888 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
889 releaseResource(userRecord.mSpiRecords, resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700890 }
891
892 /**
893 * This function finds and forcibly binds to a random system port, ensuring that the port cannot
894 * be unbound.
895 *
896 * <p>A socket cannot be un-bound from a port if it was bound to that port by number. To select
897 * a random open port and then bind by number, this function creates a temp socket, binds to a
898 * random port (specifying 0), gets that port number, and then uses is to bind the user's UDP
899 * Encapsulation Socket forcibly, so that it cannot be un-bound by the user with the returned
900 * FileHandle.
901 *
902 * <p>The loop in this function handles the inherent race window between un-binding to a port
903 * and re-binding, during which the system could *technically* hand that port out to someone
904 * else.
905 */
Benedict Wongf186d672017-10-10 20:44:28 -0700906 private int bindToRandomPort(FileDescriptor sockFd) throws IOException {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700907 for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
908 try {
909 FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
910 Os.bind(probeSocket, INADDR_ANY, 0);
911 int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
912 Os.close(probeSocket);
913 Log.v(TAG, "Binding to port " + port);
914 Os.bind(sockFd, INADDR_ANY, port);
Benedict Wongf186d672017-10-10 20:44:28 -0700915 return port;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700916 } catch (ErrnoException e) {
917 // Someone miraculously claimed the port just after we closed probeSocket.
918 if (e.errno == OsConstants.EADDRINUSE) {
919 continue;
920 }
921 throw e.rethrowAsIOException();
922 }
923 }
924 throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
925 }
Nathan Harold93962f32017-03-07 13:23:36 -0800926
927 /**
928 * Open a socket via the system server and bind it to the specified port (random if port=0).
929 * This will return a PFD to the user that represent a bound UDP socket. The system server will
930 * cache the socket and a record of its owner so that it can and must be freed when no longer
931 * needed.
932 */
933 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700934 public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
935 throws RemoteException {
936 if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) {
937 throw new IllegalArgumentException(
938 "Specified port number must be a valid non-reserved UDP port");
939 }
Nathan Harolda10003d2017-08-23 13:46:33 -0700940 checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket");
941
Benedict Wong344bd622017-11-16 15:27:22 -0800942 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700943 int resourceId = mNextResourceId.getAndIncrement();
944 FileDescriptor sockFd = null;
945 try {
Benedict Wong344bd622017-11-16 15:27:22 -0800946 if (!userRecord.mSocketQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700947 return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
948 }
949
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700950 sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
951
952 if (port != 0) {
953 Log.v(TAG, "Binding to port " + port);
954 Os.bind(sockFd, INADDR_ANY, port);
955 } else {
Benedict Wongf186d672017-10-10 20:44:28 -0700956 port = bindToRandomPort(sockFd);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700957 }
958 // This code is common to both the unspecified and specified port cases
959 Os.setsockoptInt(
960 sockFd,
961 OsConstants.IPPROTO_UDP,
962 OsConstants.UDP_ENCAP,
963 OsConstants.UDP_ENCAP_ESPINUDP);
964
Benedict Wong344bd622017-11-16 15:27:22 -0800965 userRecord.mEncapSocketRecords.put(
966 resourceId,
967 new RefcountedResource<EncapSocketRecord>(
968 new EncapSocketRecord(resourceId, sockFd, port), binder));
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700969 return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd);
970 } catch (IOException | ErrnoException e) {
971 IoUtils.closeQuietly(sockFd);
972 }
973 // If we make it to here, then something has gone wrong and we couldn't open a socket.
974 // The only reasonable condition that would cause that is resource unavailable.
975 return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
Nathan Harold93962f32017-03-07 13:23:36 -0800976 }
977
978 /** close a socket that has been been allocated by and registered with the system server */
979 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800980 public synchronized void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {
981 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
982 releaseResource(userRecord.mEncapSocketRecords, resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700983 }
Nathan Harold93962f32017-03-07 13:23:36 -0800984
985 /**
Nathan Harolda10003d2017-08-23 13:46:33 -0700986 * Checks an IpSecConfig parcel to ensure that the contents are sane and throws an
987 * IllegalArgumentException if they are not.
988 */
989 private void checkIpSecConfig(IpSecConfig config) {
Benedict Wong344bd622017-11-16 15:27:22 -0800990 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
991
Nathan Harolda10003d2017-08-23 13:46:33 -0700992 if (config.getLocalAddress() == null) {
993 throw new IllegalArgumentException("Invalid null Local InetAddress");
994 }
995
996 if (config.getRemoteAddress() == null) {
997 throw new IllegalArgumentException("Invalid null Remote InetAddress");
998 }
999
1000 switch (config.getMode()) {
1001 case IpSecTransform.MODE_TRANSPORT:
1002 if (!config.getLocalAddress().isEmpty()) {
1003 throw new IllegalArgumentException("Non-empty Local Address");
1004 }
1005 // Must be valid, and not a wildcard
1006 checkInetAddress(config.getRemoteAddress());
1007 break;
1008 case IpSecTransform.MODE_TUNNEL:
1009 break;
1010 default:
1011 throw new IllegalArgumentException(
1012 "Invalid IpSecTransform.mode: " + config.getMode());
1013 }
1014
1015 switch (config.getEncapType()) {
1016 case IpSecTransform.ENCAP_NONE:
1017 break;
1018 case IpSecTransform.ENCAP_ESPINUDP:
1019 case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
Benedict Wong344bd622017-11-16 15:27:22 -08001020 // Retrieve encap socket record; will throw IllegalArgumentException if not found
1021 userRecord.mEncapSocketRecords.getResourceOrThrow(
1022 config.getEncapSocketResourceId());
Nathan Harolda10003d2017-08-23 13:46:33 -07001023
1024 int port = config.getEncapRemotePort();
1025 if (port <= 0 || port > 0xFFFF) {
1026 throw new IllegalArgumentException("Invalid remote UDP port: " + port);
1027 }
1028 break;
1029 default:
1030 throw new IllegalArgumentException("Invalid Encap Type: " + config.getEncapType());
1031 }
1032
1033 for (int direction : DIRECTIONS) {
1034 IpSecAlgorithm crypt = config.getEncryption(direction);
1035 IpSecAlgorithm auth = config.getAuthentication(direction);
Benedict Wong0febe5e2017-08-22 21:42:33 -07001036 IpSecAlgorithm authenticatedEncryption = config.getAuthenticatedEncryption(direction);
1037 if (authenticatedEncryption == null && crypt == null && auth == null) {
1038 throw new IllegalArgumentException(
1039 "No Encryption or Authentication algorithms specified");
1040 } else if (authenticatedEncryption != null && (auth != null || crypt != null)) {
1041 throw new IllegalArgumentException(
1042 "Authenticated Encryption is mutually"
1043 + " exclusive with other Authentication or Encryption algorithms");
Nathan Harolda10003d2017-08-23 13:46:33 -07001044 }
1045
Benedict Wong344bd622017-11-16 15:27:22 -08001046 // Retrieve SPI record; will throw IllegalArgumentException if not found
1047 userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId(direction));
Nathan Harolda10003d2017-08-23 13:46:33 -07001048 }
1049 }
1050
1051 /**
Nathan Harold93962f32017-03-07 13:23:36 -08001052 * Create a transport mode transform, which represent two security associations (one in each
1053 * direction) in the kernel. The transform will be cached by the system server and must be freed
1054 * when no longer needed. It is possible to free one, deleting the SA from underneath sockets
1055 * that are using it, which will result in all of those sockets becoming unable to send or
1056 * receive data.
1057 */
1058 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001059 public synchronized IpSecTransformResponse createTransportModeTransform(
1060 IpSecConfig c, IBinder binder) throws RemoteException {
Nathan Harolda10003d2017-08-23 13:46:33 -07001061 checkIpSecConfig(c);
1062 checkNotNull(binder, "Null Binder passed to createTransportModeTransform");
Nathan Harold93962f32017-03-07 13:23:36 -08001063 int resourceId = mNextResourceId.getAndIncrement();
Benedict Wong344bd622017-11-16 15:27:22 -08001064
1065 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1066
1067 // Avoid resizing by creating a dependency array of min-size 3 (1 UDP encap + 2 SPIs)
1068 List<RefcountedResource> dependencies = new ArrayList<>(3);
1069
1070 if (!userRecord.mTransformQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -07001071 return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
1072 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001073 SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
Nathan Harolda10003d2017-08-23 13:46:33 -07001074
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001075 int encapType, encapLocalPort = 0, encapRemotePort = 0;
Benedict Wong344bd622017-11-16 15:27:22 -08001076 EncapSocketRecord socketRecord = null;
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001077 encapType = c.getEncapType();
1078 if (encapType != IpSecTransform.ENCAP_NONE) {
Benedict Wong344bd622017-11-16 15:27:22 -08001079 RefcountedResource<EncapSocketRecord> refcountedSocketRecord =
1080 userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(
1081 c.getEncapSocketResourceId());
1082 dependencies.add(refcountedSocketRecord);
1083
1084 socketRecord = refcountedSocketRecord.getResource();
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001085 encapLocalPort = socketRecord.getPort();
1086 encapRemotePort = c.getEncapRemotePort();
1087 }
1088
Nathan Harold93962f32017-03-07 13:23:36 -08001089 for (int direction : DIRECTIONS) {
1090 IpSecAlgorithm auth = c.getAuthentication(direction);
1091 IpSecAlgorithm crypt = c.getEncryption(direction);
Benedict Wong0febe5e2017-08-22 21:42:33 -07001092 IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(direction);
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001093
Benedict Wong344bd622017-11-16 15:27:22 -08001094 RefcountedResource<SpiRecord> refcountedSpiRecord =
1095 userRecord.mSpiRecords.getRefcountedResourceOrThrow(
1096 c.getSpiResourceId(direction));
1097 dependencies.add(refcountedSpiRecord);
1098
1099 spis[direction] = refcountedSpiRecord.getResource();
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001100 int spi = spis[direction].getSpi();
Nathan Harold93962f32017-03-07 13:23:36 -08001101 try {
Nathan Harolda10003d2017-08-23 13:46:33 -07001102 mSrvConfig
1103 .getNetdInstance()
ludi0f807892017-05-20 14:15:09 -07001104 .ipSecAddSecurityAssociation(
1105 resourceId,
1106 c.getMode(),
1107 direction,
Nathan Harolda10003d2017-08-23 13:46:33 -07001108 c.getLocalAddress(),
1109 c.getRemoteAddress(),
1110 (c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0,
ludi0f807892017-05-20 14:15:09 -07001111 spi,
1112 (auth != null) ? auth.getName() : "",
Manoj Boopathi Rajfffa8112017-10-26 11:49:02 -07001113 (auth != null) ? auth.getKey() : new byte[] {},
ludi0f807892017-05-20 14:15:09 -07001114 (auth != null) ? auth.getTruncationLengthBits() : 0,
1115 (crypt != null) ? crypt.getName() : "",
Manoj Boopathi Rajfffa8112017-10-26 11:49:02 -07001116 (crypt != null) ? crypt.getKey() : new byte[] {},
ludi0f807892017-05-20 14:15:09 -07001117 (crypt != null) ? crypt.getTruncationLengthBits() : 0,
Benedict Wong0febe5e2017-08-22 21:42:33 -07001118 (authCrypt != null) ? authCrypt.getName() : "",
Manoj Boopathi Rajfffa8112017-10-26 11:49:02 -07001119 (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
Benedict Wong0febe5e2017-08-22 21:42:33 -07001120 (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
ludi0f807892017-05-20 14:15:09 -07001121 encapType,
1122 encapLocalPort,
1123 encapRemotePort);
Nathan Harold93962f32017-03-07 13:23:36 -08001124 } catch (ServiceSpecificException e) {
1125 // FIXME: get the error code and throw is at an IOException from Errno Exception
ludi0f807892017-05-20 14:15:09 -07001126 return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
Nathan Harold93962f32017-03-07 13:23:36 -08001127 }
1128 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001129 // Both SAs were created successfully, time to construct a record and lock it away
Benedict Wong344bd622017-11-16 15:27:22 -08001130 userRecord.mTransformRecords.put(
1131 resourceId,
1132 new RefcountedResource<TransformRecord>(
1133 new TransformRecord(resourceId, c, spis, socketRecord),
1134 binder,
1135 dependencies.toArray(new RefcountedResource[dependencies.size()])));
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001136 return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001137 }
1138
1139 /**
1140 * Delete a transport mode transform that was previously allocated by + registered with the
1141 * system server. If this is called on an inactive (or non-existent) transform, it will not
1142 * return an error. It's safe to de-allocate transforms that may have already been deleted for
1143 * other reasons.
1144 */
1145 @Override
Benedict Wong344bd622017-11-16 15:27:22 -08001146 public synchronized void deleteTransportModeTransform(int resourceId) throws RemoteException {
1147 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1148 releaseResource(userRecord.mTransformRecords, resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001149 }
1150
1151 /**
1152 * Apply an active transport mode transform to a socket, which will apply the IPsec security
1153 * association as a correspondent policy to the provided socket
1154 */
1155 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001156 public synchronized void applyTransportModeTransform(
1157 ParcelFileDescriptor socket, int resourceId) throws RemoteException {
Benedict Wong344bd622017-11-16 15:27:22 -08001158 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
Nathan Harold93962f32017-03-07 13:23:36 -08001159
Benedict Wong344bd622017-11-16 15:27:22 -08001160 // Get transform record; if no transform is found, will throw IllegalArgumentException
1161 TransformRecord info = userRecord.mTransformRecords.getResourceOrThrow(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001162
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001163 // TODO: make this a function.
1164 if (info.pid != getCallingPid() || info.uid != getCallingUid()) {
1165 throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
1166 }
1167
1168 IpSecConfig c = info.getConfig();
1169 try {
1170 for (int direction : DIRECTIONS) {
ludi1a06aa72017-05-12 09:15:00 -07001171 mSrvConfig
1172 .getNetdInstance()
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001173 .ipSecApplyTransportModeTransform(
1174 socket.getFileDescriptor(),
1175 resourceId,
1176 direction,
Nathan Harolda10003d2017-08-23 13:46:33 -07001177 c.getLocalAddress(),
1178 c.getRemoteAddress(),
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001179 info.getSpiRecord(direction).getSpi());
Nathan Harold93962f32017-03-07 13:23:36 -08001180 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001181 } catch (ServiceSpecificException e) {
1182 // FIXME: get the error code and throw is at an IOException from Errno Exception
Nathan Harold93962f32017-03-07 13:23:36 -08001183 }
1184 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001185
Nathan Harold93962f32017-03-07 13:23:36 -08001186 /**
1187 * Remove a transport mode transform from a socket, applying the default (empty) policy. This
1188 * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of
1189 * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not
1190 * used: reserved for future improved input validation.
1191 */
1192 @Override
Benedict Wong344bd622017-11-16 15:27:22 -08001193 public synchronized void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
Nathan Harold93962f32017-03-07 13:23:36 -08001194 throws RemoteException {
1195 try {
ludi1a06aa72017-05-12 09:15:00 -07001196 mSrvConfig
1197 .getNetdInstance()
1198 .ipSecRemoveTransportModeTransform(socket.getFileDescriptor());
Nathan Harold93962f32017-03-07 13:23:36 -08001199 } catch (ServiceSpecificException e) {
1200 // FIXME: get the error code and throw is at an IOException from Errno Exception
1201 }
1202 }
1203
1204 @Override
ludib0c95b12017-05-22 10:52:23 -07001205 protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Nathan Harold1afbef42017-03-01 18:55:06 -08001206 mContext.enforceCallingOrSelfPermission(DUMP, TAG);
ludib0c95b12017-05-22 10:52:23 -07001207
1208 pw.println("IpSecService dump:");
Nathan Harold1afbef42017-03-01 18:55:06 -08001209 pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
1210 pw.println();
ludib0c95b12017-05-22 10:52:23 -07001211
Benedict Wong344bd622017-11-16 15:27:22 -08001212 pw.println("mUserResourceTracker:");
1213 pw.println(mUserResourceTracker);
Nathan Harold1afbef42017-03-01 18:55:06 -08001214 }
1215}