blob: 5c098e32045bbf72336adac95c3ab3bc8c3ab9c5 [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;
manojboopathic4be79d2017-11-30 17:11:49 -080022import static android.system.OsConstants.EINVAL;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070023import static android.system.OsConstants.IPPROTO_UDP;
24import static android.system.OsConstants.SOCK_DGRAM;
25import static com.android.internal.util.Preconditions.checkNotNull;
Nathan Harold1afbef42017-03-01 18:55:06 -080026
27import android.content.Context;
28import android.net.IIpSecService;
29import android.net.INetd;
Nathan Harold93962f32017-03-07 13:23:36 -080030import android.net.IpSecAlgorithm;
31import android.net.IpSecConfig;
32import android.net.IpSecManager;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070033import android.net.IpSecSpiResponse;
Nathan Harold93962f32017-03-07 13:23:36 -080034import android.net.IpSecTransform;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070035import android.net.IpSecTransformResponse;
36import android.net.IpSecUdpEncapResponse;
Nathan Harolda10003d2017-08-23 13:46:33 -070037import android.net.NetworkUtils;
Benedict Wongbabe5d72017-12-03 19:42:36 -080038import android.net.TrafficStats;
Nathan Harold1afbef42017-03-01 18:55:06 -080039import android.net.util.NetdService;
Nathan Harold93962f32017-03-07 13:23:36 -080040import android.os.Binder;
Nathan Harold93962f32017-03-07 13:23:36 -080041import android.os.IBinder;
42import android.os.ParcelFileDescriptor;
Nathan Harold1afbef42017-03-01 18:55:06 -080043import android.os.RemoteException;
Nathan Harold93962f32017-03-07 13:23:36 -080044import android.os.ServiceSpecificException;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070045import android.system.ErrnoException;
46import android.system.Os;
47import android.system.OsConstants;
Nathan Harolda10003d2017-08-23 13:46:33 -070048import android.text.TextUtils;
Nathan Harold1afbef42017-03-01 18:55:06 -080049import android.util.Log;
50import android.util.Slog;
Nathan Harold93962f32017-03-07 13:23:36 -080051import android.util.SparseArray;
Nathan Harolda10003d2017-08-23 13:46:33 -070052
Nathan Harold93962f32017-03-07 13:23:36 -080053import com.android.internal.annotations.GuardedBy;
ludi1a06aa72017-05-12 09:15:00 -070054import com.android.internal.annotations.VisibleForTesting;
Nathan Harolda10003d2017-08-23 13:46:33 -070055
Nathan Harold1afbef42017-03-01 18:55:06 -080056import java.io.FileDescriptor;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070057import java.io.IOException;
Nathan Harold1afbef42017-03-01 18:55:06 -080058import java.io.PrintWriter;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070059import java.net.InetAddress;
60import java.net.InetSocketAddress;
61import java.net.UnknownHostException;
Benedict Wong409c8ca2017-10-26 19:41:43 -070062import java.util.ArrayList;
63import java.util.List;
Nathan Harold93962f32017-03-07 13:23:36 -080064import java.util.concurrent.atomic.AtomicInteger;
Nathan Harolda10003d2017-08-23 13:46:33 -070065
Nathan Harold8dc1fd02017-04-04 19:37:48 -070066import libcore.io.IoUtils;
Nathan Harold1afbef42017-03-01 18:55:06 -080067
Benedict Wong409c8ca2017-10-26 19:41:43 -070068/**
69 * A service to manage multiple clients that want to access the IpSec API. The service is
70 * responsible for maintaining a list of clients and managing the resources (and related quotas)
71 * that each of them own.
72 *
73 * <p>Synchronization in IpSecService is done on all entrypoints due to potential race conditions at
74 * the kernel/xfrm level. Further, this allows the simplifying assumption to be made that only one
75 * thread is ever running at a time.
76 *
77 * @hide
78 */
Nathan Harold1afbef42017-03-01 18:55:06 -080079public class IpSecService extends IIpSecService.Stub {
80 private static final String TAG = "IpSecService";
81 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
Nathan Harold8dc1fd02017-04-04 19:37:48 -070082
Nathan Harold1afbef42017-03-01 18:55:06 -080083 private static final String NETD_SERVICE_NAME = "netd";
Nathan Harold93962f32017-03-07 13:23:36 -080084 private static final int[] DIRECTIONS =
85 new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN};
Nathan Harold1afbef42017-03-01 18:55:06 -080086
ludi1a06aa72017-05-12 09:15:00 -070087 private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms
Nathan Harold8dc1fd02017-04-04 19:37:48 -070088 private static final int MAX_PORT_BIND_ATTEMPTS = 10;
89 private static final InetAddress INADDR_ANY;
90
91 static {
92 try {
93 INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
94 } catch (UnknownHostException e) {
95 throw new RuntimeException(e);
96 }
97 }
98
99 static final int FREE_PORT_MIN = 1024; // ports 1-1023 are reserved
100 static final int PORT_MAX = 0xFFFF; // ports are an unsigned 16-bit integer
101
102 /* Binder context for this service */
Nathan Harold1afbef42017-03-01 18:55:06 -0800103 private final Context mContext;
104
Nathan Haroldd8c74292017-12-13 19:16:33 -0800105 /**
106 * The next non-repeating global ID for tracking resources between users, this service,
107 * and kernel data structures. Accessing this variable is not thread safe, so it is
108 * only read or modified within blocks synchronized on IpSecService.this. We want to
109 * avoid -1 (INVALID_RESOURCE_ID) and 0 (we probably forgot to initialize it).
110 */
111 @GuardedBy("IpSecService.this")
112 private int mNextResourceId = 1;
Nathan Harold1afbef42017-03-01 18:55:06 -0800113
ludi1a06aa72017-05-12 09:15:00 -0700114 interface IpSecServiceConfiguration {
115 INetd getNetdInstance() throws RemoteException;
116
117 static IpSecServiceConfiguration GETSRVINSTANCE =
118 new IpSecServiceConfiguration() {
119 @Override
120 public INetd getNetdInstance() throws RemoteException {
121 final INetd netd = NetdService.getInstance();
122 if (netd == null) {
123 throw new RemoteException("Failed to Get Netd Instance");
124 }
125 return netd;
126 }
127 };
128 }
129
130 private final IpSecServiceConfiguration mSrvConfig;
Benedict Wongbabe5d72017-12-03 19:42:36 -0800131 final UidFdTagger mUidFdTagger;
ludi1a06aa72017-05-12 09:15:00 -0700132
Benedict Wong409c8ca2017-10-26 19:41:43 -0700133 /**
134 * Interface for user-reference and kernel-resource cleanup.
135 *
136 * <p>This interface must be implemented for a resource to be reference counted.
137 */
138 @VisibleForTesting
139 public interface IResource {
140 /**
141 * Invalidates a IResource object, ensuring it is invalid for the purposes of allocating new
142 * objects dependent on it.
143 *
144 * <p>Implementations of this method are expected to remove references to the IResource
145 * object from the IpSecService's tracking arrays. The removal from the arrays ensures that
146 * the resource is considered invalid for user access or allocation or use in other
147 * resources.
148 *
149 * <p>References to the IResource object may be held by other RefcountedResource objects,
150 * and as such, the kernel resources and quota may not be cleaned up.
151 */
152 void invalidate() throws RemoteException;
153
154 /**
155 * Releases underlying resources and related quotas.
156 *
157 * <p>Implementations of this method are expected to remove all system resources that are
158 * tracked by the IResource object. Due to other RefcountedResource objects potentially
Benedict Wong344bd622017-11-16 15:27:22 -0800159 * having references to the IResource object, freeUnderlyingResources may not always be
Benedict Wong409c8ca2017-10-26 19:41:43 -0700160 * called from releaseIfUnreferencedRecursively().
161 */
162 void freeUnderlyingResources() throws RemoteException;
163 }
164
165 /**
166 * RefcountedResource manages references and dependencies in an exclusively acyclic graph.
167 *
168 * <p>RefcountedResource implements both explicit and implicit resource management. Creating a
169 * RefcountedResource object creates an explicit reference that must be freed by calling
170 * userRelease(). Additionally, adding this object as a child of another RefcountedResource
171 * object will add an implicit reference.
172 *
173 * <p>Resources are cleaned up when all references, both implicit and explicit, are released
174 * (ie, when userRelease() is called and when all parents have called releaseReference() on this
175 * object.)
176 */
177 @VisibleForTesting
178 public class RefcountedResource<T extends IResource> implements IBinder.DeathRecipient {
179 private final T mResource;
180 private final List<RefcountedResource> mChildren;
181 int mRefCount = 1; // starts at 1 for user's reference.
182 IBinder mBinder;
183
184 RefcountedResource(T resource, IBinder binder, RefcountedResource... children) {
185 synchronized (IpSecService.this) {
186 this.mResource = resource;
187 this.mChildren = new ArrayList<>(children.length);
188 this.mBinder = binder;
189
190 for (RefcountedResource child : children) {
191 mChildren.add(child);
192 child.mRefCount++;
193 }
194
195 try {
196 mBinder.linkToDeath(this, 0);
197 } catch (RemoteException e) {
198 binderDied();
199 }
200 }
201 }
202
203 /**
204 * If the Binder object dies, this function is called to free the system resources that are
Benedict Wong344bd622017-11-16 15:27:22 -0800205 * being tracked by this record and to subsequently release this record for garbage
Benedict Wong409c8ca2017-10-26 19:41:43 -0700206 * collection
207 */
208 @Override
209 public void binderDied() {
210 synchronized (IpSecService.this) {
211 try {
212 userRelease();
213 } catch (Exception e) {
214 Log.e(TAG, "Failed to release resource: " + e);
215 }
216 }
217 }
218
219 public T getResource() {
220 return mResource;
221 }
222
223 /**
224 * Unlinks from binder and performs IpSecService resource cleanup (removes from resource
225 * arrays)
226 *
227 * <p>If this method has been previously called, the RefcountedResource's binder field will
228 * be null, and the method will return without performing the cleanup a second time.
229 *
230 * <p>Note that calling this function does not imply that kernel resources will be freed at
231 * this time, or that the related quota will be returned. Such actions will only be
232 * performed upon the reference count reaching zero.
233 */
234 @GuardedBy("IpSecService.this")
235 public void userRelease() throws RemoteException {
236 // Prevent users from putting reference counts into a bad state by calling
237 // userRelease() multiple times.
238 if (mBinder == null) {
239 return;
240 }
241
242 mBinder.unlinkToDeath(this, 0);
243 mBinder = null;
244
245 mResource.invalidate();
246
247 releaseReference();
248 }
249
250 /**
251 * Removes a reference to this resource. If the resultant reference count is zero, the
252 * underlying resources are freed, and references to all child resources are also dropped
253 * recursively (resulting in them freeing their resources and children, etcetera)
254 *
255 * <p>This method also sets the reference count to an invalid value (-1) to signify that it
256 * has been fully released. Any subsequent calls to this method will result in an
257 * IllegalStateException being thrown due to resource already having been previously
258 * released
259 */
260 @VisibleForTesting
261 @GuardedBy("IpSecService.this")
262 public void releaseReference() throws RemoteException {
263 mRefCount--;
264
265 if (mRefCount > 0) {
266 return;
267 } else if (mRefCount < 0) {
268 throw new IllegalStateException(
269 "Invalid operation - resource has already been released.");
270 }
271
272 // Cleanup own resources
273 mResource.freeUnderlyingResources();
274
275 // Cleanup child resources as needed
276 for (RefcountedResource<? extends IResource> child : mChildren) {
277 child.releaseReference();
278 }
279
280 // Enforce that resource cleanup can only be called once
281 // By decrementing the refcount (from 0 to -1), the next call will throw an
282 // IllegalStateException - it has already been released fully.
283 mRefCount--;
284 }
285
286 @Override
287 public String toString() {
288 return new StringBuilder()
289 .append("{mResource=")
290 .append(mResource)
291 .append(", mRefCount=")
292 .append(mRefCount)
293 .append(", mChildren=")
294 .append(mChildren)
295 .append("}")
296 .toString();
297 }
298 }
299
Nathan Harolda1afbd82017-04-24 16:16:34 -0700300 /* Very simple counting class that looks much like a counting semaphore */
Benedict Wong344bd622017-11-16 15:27:22 -0800301 @VisibleForTesting
302 static class ResourceTracker {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700303 private final int mMax;
304 int mCurrent;
305
306 ResourceTracker(int max) {
307 mMax = max;
308 mCurrent = 0;
309 }
310
Benedict Wong344bd622017-11-16 15:27:22 -0800311 boolean isAvailable() {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700312 return (mCurrent < mMax);
313 }
314
Benedict Wong344bd622017-11-16 15:27:22 -0800315 void take() {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700316 if (!isAvailable()) {
317 Log.wtf(TAG, "Too many resources allocated!");
318 }
319 mCurrent++;
320 }
321
Benedict Wong344bd622017-11-16 15:27:22 -0800322 void give() {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700323 if (mCurrent <= 0) {
324 Log.wtf(TAG, "We've released this resource too many times");
325 }
326 mCurrent--;
327 }
ludi3e5ea232017-08-10 15:44:40 -0700328
329 @Override
330 public String toString() {
331 return new StringBuilder()
332 .append("{mCurrent=")
333 .append(mCurrent)
334 .append(", mMax=")
335 .append(mMax)
336 .append("}")
337 .toString();
338 }
Nathan Harolda1afbd82017-04-24 16:16:34 -0700339 }
340
Benedict Wong344bd622017-11-16 15:27:22 -0800341 @VisibleForTesting
342 static final class UserRecord {
343 /* Type names */
344 public static final String TYPENAME_SPI = "SecurityParameterIndex";
345 public static final String TYPENAME_TRANSFORM = "IpSecTransform";
346 public static final String TYPENAME_ENCAP_SOCKET = "UdpEncapSocket";
347
348 /* Maximum number of each type of resource that a single UID may possess */
Nathan Harolda1afbd82017-04-24 16:16:34 -0700349 public static final int MAX_NUM_ENCAP_SOCKETS = 2;
Nathan Harolda1afbd82017-04-24 16:16:34 -0700350 public static final int MAX_NUM_TRANSFORMS = 4;
Nathan Harolda1afbd82017-04-24 16:16:34 -0700351 public static final int MAX_NUM_SPIS = 8;
352
Benedict Wong344bd622017-11-16 15:27:22 -0800353 final RefcountedResourceArray<SpiRecord> mSpiRecords =
354 new RefcountedResourceArray<>(TYPENAME_SPI);
355 final ResourceTracker mSpiQuotaTracker = new ResourceTracker(MAX_NUM_SPIS);
ludi3e5ea232017-08-10 15:44:40 -0700356
Benedict Wong344bd622017-11-16 15:27:22 -0800357 final RefcountedResourceArray<TransformRecord> mTransformRecords =
358 new RefcountedResourceArray<>(TYPENAME_TRANSFORM);
359 final ResourceTracker mTransformQuotaTracker = new ResourceTracker(MAX_NUM_TRANSFORMS);
360
361 final RefcountedResourceArray<EncapSocketRecord> mEncapSocketRecords =
362 new RefcountedResourceArray<>(TYPENAME_ENCAP_SOCKET);
363 final ResourceTracker mSocketQuotaTracker = new ResourceTracker(MAX_NUM_ENCAP_SOCKETS);
364
365 void removeSpiRecord(int resourceId) {
366 mSpiRecords.remove(resourceId);
Nathan Harolda1afbd82017-04-24 16:16:34 -0700367 }
368
Benedict Wong344bd622017-11-16 15:27:22 -0800369 void removeTransformRecord(int resourceId) {
370 mTransformRecords.remove(resourceId);
371 }
372
373 void removeEncapSocketRecord(int resourceId) {
374 mEncapSocketRecords.remove(resourceId);
375 }
376
377 @Override
378 public String toString() {
379 return new StringBuilder()
380 .append("{mSpiQuotaTracker=")
381 .append(mSpiQuotaTracker)
382 .append(", mTransformQuotaTracker=")
383 .append(mTransformQuotaTracker)
384 .append(", mSocketQuotaTracker=")
385 .append(mSocketQuotaTracker)
386 .append(", mSpiRecords=")
387 .append(mSpiRecords)
388 .append(", mTransformRecords=")
389 .append(mTransformRecords)
390 .append(", mEncapSocketRecords=")
391 .append(mEncapSocketRecords)
392 .append("}")
393 .toString();
394 }
395 }
396
397 @VisibleForTesting
398 static final class UserResourceTracker {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700399 private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
400
Benedict Wong344bd622017-11-16 15:27:22 -0800401 /** Never-fail getter that populates the list of UIDs as-needed */
402 public UserRecord getUserRecord(int uid) {
403 checkCallerUid(uid);
404
Nathan Harolda1afbd82017-04-24 16:16:34 -0700405 UserRecord r = mUserRecords.get(uid);
406 if (r == null) {
407 r = new UserRecord();
408 mUserRecords.put(uid, r);
409 }
410 return r;
411 }
ludi3e5ea232017-08-10 15:44:40 -0700412
Benedict Wong344bd622017-11-16 15:27:22 -0800413 /** Safety method; guards against access of other user's UserRecords */
414 private void checkCallerUid(int uid) {
415 if (uid != Binder.getCallingUid()
416 && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
417 throw new SecurityException("Attempted access of unowned resources");
418 }
419 }
420
ludi3e5ea232017-08-10 15:44:40 -0700421 @Override
422 public String toString() {
423 return mUserRecords.toString();
424 }
Nathan Harolda1afbd82017-04-24 16:16:34 -0700425 }
426
Benedict Wong344bd622017-11-16 15:27:22 -0800427 @VisibleForTesting final UserResourceTracker mUserResourceTracker = new UserResourceTracker();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700428
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700429 /**
Benedict Wong344bd622017-11-16 15:27:22 -0800430 * The KernelResourceRecord class provides a facility to cleanly and reliably track system
431 * resources. It relies on a provided resourceId that should uniquely identify the kernel
432 * resource. To use this class, the user should implement the invalidate() and
433 * freeUnderlyingResources() methods that are responsible for cleaning up IpSecService resource
434 * tracking arrays and kernel resources, respectively
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700435 */
Benedict Wong344bd622017-11-16 15:27:22 -0800436 private abstract class KernelResourceRecord implements IResource {
Nathan Harold93962f32017-03-07 13:23:36 -0800437 final int pid;
438 final int uid;
Benedict Wong344bd622017-11-16 15:27:22 -0800439 protected final int mResourceId;
Nathan Harold93962f32017-03-07 13:23:36 -0800440
Benedict Wong344bd622017-11-16 15:27:22 -0800441 KernelResourceRecord(int resourceId) {
Nathan Harold93962f32017-03-07 13:23:36 -0800442 super();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700443 if (resourceId == INVALID_RESOURCE_ID) {
444 throw new IllegalArgumentException("Resource ID must not be INVALID_RESOURCE_ID");
445 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700446 mResourceId = resourceId;
Nathan Harold93962f32017-03-07 13:23:36 -0800447 pid = Binder.getCallingPid();
448 uid = Binder.getCallingUid();
449
Nathan Harolda1afbd82017-04-24 16:16:34 -0700450 getResourceTracker().take();
Nathan Harold93962f32017-03-07 13:23:36 -0800451 }
452
Benedict Wong344bd622017-11-16 15:27:22 -0800453 @Override
454 public abstract void invalidate() throws RemoteException;
455
456 /** Convenience method; retrieves the user resource record for the stored UID. */
457 protected UserRecord getUserRecord() {
458 return mUserResourceTracker.getUserRecord(uid);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700459 }
460
Benedict Wong344bd622017-11-16 15:27:22 -0800461 @Override
462 public abstract void freeUnderlyingResources() throws RemoteException;
ludib0c95b12017-05-22 10:52:23 -0700463
Nathan Harolda1afbd82017-04-24 16:16:34 -0700464 /** Get the resource tracker for this resource */
465 protected abstract ResourceTracker getResourceTracker();
466
ludib0c95b12017-05-22 10:52:23 -0700467 @Override
468 public String toString() {
469 return new StringBuilder()
470 .append("{mResourceId=")
471 .append(mResourceId)
472 .append(", pid=")
473 .append(pid)
474 .append(", uid=")
475 .append(uid)
ludib0c95b12017-05-22 10:52:23 -0700476 .append("}")
477 .toString();
478 }
Nathan Harold93962f32017-03-07 13:23:36 -0800479 };
480
Benedict Wong344bd622017-11-16 15:27:22 -0800481 // TODO: Move this to right after RefcountedResource. With this here, Gerrit was showing many
482 // more things as changed.
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700483 /**
Benedict Wong344bd622017-11-16 15:27:22 -0800484 * Thin wrapper over SparseArray to ensure resources exist, and simplify generic typing.
485 *
486 * <p>RefcountedResourceArray prevents null insertions, and throws an IllegalArgumentException
487 * if a key is not found during a retrieval process.
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700488 */
Benedict Wong344bd622017-11-16 15:27:22 -0800489 static class RefcountedResourceArray<T extends IResource> {
490 SparseArray<RefcountedResource<T>> mArray = new SparseArray<>();
491 private final String mTypeName;
Nathan Harold93962f32017-03-07 13:23:36 -0800492
Benedict Wong344bd622017-11-16 15:27:22 -0800493 public RefcountedResourceArray(String typeName) {
494 this.mTypeName = typeName;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700495 }
496
Benedict Wong344bd622017-11-16 15:27:22 -0800497 /**
498 * Accessor method to get inner resource object.
499 *
500 * @throws IllegalArgumentException if no resource with provided key is found.
501 */
502 T getResourceOrThrow(int key) {
503 return getRefcountedResourceOrThrow(key).getResource();
504 }
505
506 /**
507 * Accessor method to get reference counting wrapper.
508 *
509 * @throws IllegalArgumentException if no resource with provided key is found.
510 */
511 RefcountedResource<T> getRefcountedResourceOrThrow(int key) {
512 RefcountedResource<T> resource = mArray.get(key);
513 if (resource == null) {
514 throw new IllegalArgumentException(
515 String.format("No such %s found for given id: %d", mTypeName, key));
516 }
517
518 return resource;
519 }
520
521 void put(int key, RefcountedResource<T> obj) {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700522 checkNotNull(obj, "Null resources cannot be added");
523 mArray.put(key, obj);
524 }
525
526 void remove(int key) {
527 mArray.remove(key);
528 }
ludib0c95b12017-05-22 10:52:23 -0700529
530 @Override
531 public String toString() {
532 return mArray.toString();
533 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700534 }
535
Benedict Wong344bd622017-11-16 15:27:22 -0800536 private final class TransformRecord extends KernelResourceRecord {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700537 private final IpSecConfig mConfig;
538 private final SpiRecord[] mSpis;
Benedict Wong344bd622017-11-16 15:27:22 -0800539 private final EncapSocketRecord mSocket;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700540
541 TransformRecord(
Benedict Wong344bd622017-11-16 15:27:22 -0800542 int resourceId, IpSecConfig config, SpiRecord[] spis, EncapSocketRecord socket) {
543 super(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800544 mConfig = config;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700545 mSpis = spis;
546 mSocket = socket;
Nathan Harold93962f32017-03-07 13:23:36 -0800547 }
548
549 public IpSecConfig getConfig() {
550 return mConfig;
551 }
552
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700553 public SpiRecord getSpiRecord(int direction) {
554 return mSpis[direction];
555 }
556
557 /** always guarded by IpSecService#this */
Nathan Harold93962f32017-03-07 13:23:36 -0800558 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800559 public void freeUnderlyingResources() {
Nathan Harold93962f32017-03-07 13:23:36 -0800560 for (int direction : DIRECTIONS) {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700561 int spi = mSpis[direction].getSpi();
Nathan Harold93962f32017-03-07 13:23:36 -0800562 try {
ludi1a06aa72017-05-12 09:15:00 -0700563 mSrvConfig
564 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800565 .ipSecDeleteSecurityAssociation(
566 mResourceId,
567 direction,
Nathan Harolda10003d2017-08-23 13:46:33 -0700568 mConfig.getLocalAddress(),
569 mConfig.getRemoteAddress(),
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700570 spi);
Nathan Harold93962f32017-03-07 13:23:36 -0800571 } catch (ServiceSpecificException e) {
572 // FIXME: get the error code and throw is at an IOException from Errno Exception
573 } catch (RemoteException e) {
574 Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
575 }
576 }
Nathan Harold93962f32017-03-07 13:23:36 -0800577
Benedict Wong344bd622017-11-16 15:27:22 -0800578 getResourceTracker().give();
Nathan Harold93962f32017-03-07 13:23:36 -0800579 }
ludib0c95b12017-05-22 10:52:23 -0700580
Benedict Wong344bd622017-11-16 15:27:22 -0800581 @Override
582 public void invalidate() throws RemoteException {
583 getUserRecord().removeTransformRecord(mResourceId);
584 }
585
586 @Override
Nathan Harolda1afbd82017-04-24 16:16:34 -0700587 protected ResourceTracker getResourceTracker() {
Benedict Wong344bd622017-11-16 15:27:22 -0800588 return getUserRecord().mTransformQuotaTracker;
Nathan Harolda1afbd82017-04-24 16:16:34 -0700589 }
590
ludib0c95b12017-05-22 10:52:23 -0700591 @Override
592 public String toString() {
593 StringBuilder strBuilder = new StringBuilder();
594 strBuilder
595 .append("{super=")
596 .append(super.toString())
597 .append(", mSocket=")
598 .append(mSocket)
599 .append(", mSpis[OUT].mResourceId=")
600 .append(mSpis[IpSecTransform.DIRECTION_OUT].mResourceId)
601 .append(", mSpis[IN].mResourceId=")
602 .append(mSpis[IpSecTransform.DIRECTION_IN].mResourceId)
603 .append(", mConfig=")
604 .append(mConfig)
605 .append("}");
606 return strBuilder.toString();
607 }
Nathan Harold93962f32017-03-07 13:23:36 -0800608 }
609
Benedict Wong344bd622017-11-16 15:27:22 -0800610 private final class SpiRecord extends KernelResourceRecord {
Nathan Harold93962f32017-03-07 13:23:36 -0800611 private final int mDirection;
612 private final String mLocalAddress;
613 private final String mRemoteAddress;
Nathan Harold93962f32017-03-07 13:23:36 -0800614 private int mSpi;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700615
616 private boolean mOwnedByTransform = false;
Nathan Harold93962f32017-03-07 13:23:36 -0800617
618 SpiRecord(
619 int resourceId,
620 int direction,
621 String localAddress,
622 String remoteAddress,
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700623 int spi) {
Benedict Wong344bd622017-11-16 15:27:22 -0800624 super(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800625 mDirection = direction;
626 mLocalAddress = localAddress;
627 mRemoteAddress = remoteAddress;
628 mSpi = spi;
Nathan Harold93962f32017-03-07 13:23:36 -0800629 }
630
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700631 /** always guarded by IpSecService#this */
632 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800633 public void freeUnderlyingResources() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700634 if (mOwnedByTransform) {
635 Log.d(TAG, "Cannot release Spi " + mSpi + ": Currently locked by a Transform");
636 // Because SPIs are "handed off" to transform, objects, they should never be
637 // freed from the SpiRecord once used in a transform. (They refer to the same SA,
638 // thus ownership and responsibility for freeing these resources passes to the
639 // Transform object). Thus, we should let the user free them without penalty once
640 // they are applied in a Transform object.
641 return;
642 }
643
Nathan Harold93962f32017-03-07 13:23:36 -0800644 try {
ludi1a06aa72017-05-12 09:15:00 -0700645 mSrvConfig
646 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800647 .ipSecDeleteSecurityAssociation(
648 mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi);
649 } catch (ServiceSpecificException e) {
650 // FIXME: get the error code and throw is at an IOException from Errno Exception
651 } catch (RemoteException e) {
652 Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId);
653 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700654
655 mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
Nathan Harold93962f32017-03-07 13:23:36 -0800656
Benedict Wong344bd622017-11-16 15:27:22 -0800657 getResourceTracker().give();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700658 }
659
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700660 public int getSpi() {
661 return mSpi;
662 }
663
664 public void setOwnedByTransform() {
665 if (mOwnedByTransform) {
666 // Programming error
Andreas Gamped6d8e452017-07-11 10:25:09 -0700667 throw new IllegalStateException("Cannot own an SPI twice!");
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700668 }
669
670 mOwnedByTransform = true;
Nathan Harold93962f32017-03-07 13:23:36 -0800671 }
ludib0c95b12017-05-22 10:52:23 -0700672
673 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800674 public void invalidate() throws RemoteException {
675 getUserRecord().removeSpiRecord(mResourceId);
676 }
677
678 @Override
679 protected ResourceTracker getResourceTracker() {
680 return getUserRecord().mSpiQuotaTracker;
681 }
682
683 @Override
ludib0c95b12017-05-22 10:52:23 -0700684 public String toString() {
685 StringBuilder strBuilder = new StringBuilder();
686 strBuilder
687 .append("{super=")
688 .append(super.toString())
689 .append(", mSpi=")
690 .append(mSpi)
691 .append(", mDirection=")
692 .append(mDirection)
693 .append(", mLocalAddress=")
694 .append(mLocalAddress)
695 .append(", mRemoteAddress=")
696 .append(mRemoteAddress)
697 .append(", mOwnedByTransform=")
698 .append(mOwnedByTransform)
699 .append("}");
700 return strBuilder.toString();
701 }
Nathan Harold93962f32017-03-07 13:23:36 -0800702 }
703
Benedict Wong344bd622017-11-16 15:27:22 -0800704 private final class EncapSocketRecord extends KernelResourceRecord {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700705 private FileDescriptor mSocket;
706 private final int mPort;
Nathan Harold93962f32017-03-07 13:23:36 -0800707
Benedict Wong344bd622017-11-16 15:27:22 -0800708 EncapSocketRecord(int resourceId, FileDescriptor socket, int port) {
709 super(resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700710 mSocket = socket;
711 mPort = port;
712 }
713
714 /** always guarded by IpSecService#this */
715 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800716 public void freeUnderlyingResources() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700717 Log.d(TAG, "Closing port " + mPort);
718 IoUtils.closeQuietly(mSocket);
719 mSocket = null;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700720
Benedict Wong344bd622017-11-16 15:27:22 -0800721 getResourceTracker().give();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700722 }
723
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700724 public int getPort() {
725 return mPort;
726 }
727
728 public FileDescriptor getSocket() {
729 return mSocket;
730 }
ludib0c95b12017-05-22 10:52:23 -0700731
732 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800733 protected ResourceTracker getResourceTracker() {
734 return getUserRecord().mSocketQuotaTracker;
735 }
736
737 @Override
738 public void invalidate() {
739 getUserRecord().removeEncapSocketRecord(mResourceId);
740 }
741
742 @Override
ludib0c95b12017-05-22 10:52:23 -0700743 public String toString() {
744 return new StringBuilder()
745 .append("{super=")
746 .append(super.toString())
747 .append(", mSocket=")
748 .append(mSocket)
749 .append(", mPort=")
750 .append(mPort)
751 .append("}")
752 .toString();
753 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700754 }
Nathan Harold93962f32017-03-07 13:23:36 -0800755
Nathan Harold1afbef42017-03-01 18:55:06 -0800756 /**
757 * Constructs a new IpSecService instance
758 *
759 * @param context Binder context for this service
760 */
761 private IpSecService(Context context) {
ludi1a06aa72017-05-12 09:15:00 -0700762 this(context, IpSecServiceConfiguration.GETSRVINSTANCE);
Nathan Harold1afbef42017-03-01 18:55:06 -0800763 }
764
765 static IpSecService create(Context context) throws InterruptedException {
766 final IpSecService service = new IpSecService(context);
767 service.connectNativeNetdService();
768 return service;
769 }
770
ludi1a06aa72017-05-12 09:15:00 -0700771 /** @hide */
772 @VisibleForTesting
773 public IpSecService(Context context, IpSecServiceConfiguration config) {
Benedict Wongbabe5d72017-12-03 19:42:36 -0800774 this(context, config, (fd, uid) -> {
775 try{
776 TrafficStats.setThreadStatsUid(uid);
777 TrafficStats.tagFileDescriptor(fd);
778 } finally {
779 TrafficStats.clearThreadStatsUid();
780 }
781 });
782 }
783
784 /** @hide */
785 @VisibleForTesting
786 public IpSecService(
787 Context context, IpSecServiceConfiguration config, UidFdTagger uidFdTagger) {
ludi1a06aa72017-05-12 09:15:00 -0700788 mContext = context;
789 mSrvConfig = config;
Benedict Wongbabe5d72017-12-03 19:42:36 -0800790 mUidFdTagger = uidFdTagger;
ludi1a06aa72017-05-12 09:15:00 -0700791 }
792
Nathan Harold1afbef42017-03-01 18:55:06 -0800793 public void systemReady() {
794 if (isNetdAlive()) {
795 Slog.d(TAG, "IpSecService is ready");
796 } else {
797 Slog.wtf(TAG, "IpSecService not ready: failed to connect to NetD Native Service!");
798 }
799 }
800
801 private void connectNativeNetdService() {
802 // Avoid blocking the system server to do this
Nathan Haroldb0e05082017-07-17 14:01:53 -0700803 new Thread() {
804 @Override
805 public void run() {
806 synchronized (IpSecService.this) {
ludi1a06aa72017-05-12 09:15:00 -0700807 NetdService.get(NETD_FETCH_TIMEOUT_MS);
Nathan Haroldb0e05082017-07-17 14:01:53 -0700808 }
809 }
810 }.start();
Nathan Harold1afbef42017-03-01 18:55:06 -0800811 }
812
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700813 synchronized boolean isNetdAlive() {
814 try {
ludi1a06aa72017-05-12 09:15:00 -0700815 final INetd netd = mSrvConfig.getNetdInstance();
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700816 if (netd == null) {
Nathan Harold1afbef42017-03-01 18:55:06 -0800817 return false;
818 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700819 return netd.isAlive();
820 } catch (RemoteException re) {
821 return false;
Nathan Harold1afbef42017-03-01 18:55:06 -0800822 }
823 }
824
Nathan Harolda10003d2017-08-23 13:46:33 -0700825 /**
826 * Checks that the provided InetAddress is valid for use in an IPsec SA. The address must not be
827 * a wildcard address and must be in a numeric form such as 1.2.3.4 or 2001::1.
828 */
829 private static void checkInetAddress(String inetAddress) {
830 if (TextUtils.isEmpty(inetAddress)) {
831 throw new IllegalArgumentException("Unspecified address");
832 }
833
834 InetAddress checkAddr = NetworkUtils.numericToInetAddress(inetAddress);
835
836 if (checkAddr.isAnyLocalAddress()) {
837 throw new IllegalArgumentException("Inappropriate wildcard address: " + inetAddress);
838 }
839 }
840
841 /**
842 * Checks the user-provided direction field and throws an IllegalArgumentException if it is not
843 * DIRECTION_IN or DIRECTION_OUT
844 */
845 private static void checkDirection(int direction) {
846 switch (direction) {
847 case IpSecTransform.DIRECTION_OUT:
848 case IpSecTransform.DIRECTION_IN:
849 return;
850 }
851 throw new IllegalArgumentException("Invalid Direction: " + direction);
852 }
853
Nathan Harold93962f32017-03-07 13:23:36 -0800854 /** Get a new SPI and maintain the reservation in the system server */
Jonathan Basseri5fb92902017-11-16 10:58:01 -0800855 @Override
856 public synchronized IpSecSpiResponse allocateSecurityParameterIndex(
Nathan Harold93962f32017-03-07 13:23:36 -0800857 int direction, String remoteAddress, int requestedSpi, IBinder binder)
858 throws RemoteException {
Nathan Harolda10003d2017-08-23 13:46:33 -0700859 checkDirection(direction);
860 checkInetAddress(remoteAddress);
861 /* requestedSpi can be anything in the int range, so no check is needed. */
Jonathan Basseri5fb92902017-11-16 10:58:01 -0800862 checkNotNull(binder, "Null Binder passed to allocateSecurityParameterIndex");
Nathan Harolda10003d2017-08-23 13:46:33 -0700863
Benedict Wong344bd622017-11-16 15:27:22 -0800864 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
Nathan Haroldd8c74292017-12-13 19:16:33 -0800865 final int resourceId = mNextResourceId++;
Nathan Harold93962f32017-03-07 13:23:36 -0800866
867 int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
868 String localAddress = "";
Nathan Harolda1afbd82017-04-24 16:16:34 -0700869
Nathan Harold93962f32017-03-07 13:23:36 -0800870 try {
Benedict Wong344bd622017-11-16 15:27:22 -0800871 if (!userRecord.mSpiQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700872 return new IpSecSpiResponse(
Nathan Harolda10003d2017-08-23 13:46:33 -0700873 IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
Nathan Harolda1afbd82017-04-24 16:16:34 -0700874 }
Nathan Harold93962f32017-03-07 13:23:36 -0800875 spi =
ludi1a06aa72017-05-12 09:15:00 -0700876 mSrvConfig
877 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800878 .ipSecAllocateSpi(
879 resourceId,
880 direction,
881 localAddress,
882 remoteAddress,
883 requestedSpi);
884 Log.d(TAG, "Allocated SPI " + spi);
Benedict Wong344bd622017-11-16 15:27:22 -0800885 userRecord.mSpiRecords.put(
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700886 resourceId,
Benedict Wong344bd622017-11-16 15:27:22 -0800887 new RefcountedResource<SpiRecord>(
888 new SpiRecord(resourceId, direction, localAddress, remoteAddress, spi),
889 binder));
Nathan Harold93962f32017-03-07 13:23:36 -0800890 } catch (ServiceSpecificException e) {
891 // TODO: Add appropriate checks when other ServiceSpecificException types are supported
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700892 return new IpSecSpiResponse(
Nathan Harolda1afbd82017-04-24 16:16:34 -0700893 IpSecManager.Status.SPI_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
Nathan Harold93962f32017-03-07 13:23:36 -0800894 } catch (RemoteException e) {
895 throw e.rethrowFromSystemServer();
896 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700897 return new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, spi);
898 }
899
900 /* This method should only be called from Binder threads. Do not call this from
901 * within the system server as it will crash the system on failure.
902 */
Benedict Wong344bd622017-11-16 15:27:22 -0800903 private void releaseResource(RefcountedResourceArray resArray, int resourceId)
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700904 throws RemoteException {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700905
Benedict Wong344bd622017-11-16 15:27:22 -0800906 resArray.getRefcountedResourceOrThrow(resourceId).userRelease();
Nathan Harold93962f32017-03-07 13:23:36 -0800907 }
908
909 /** Release a previously allocated SPI that has been registered with the system server */
910 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800911 public synchronized void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
912 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
913 releaseResource(userRecord.mSpiRecords, resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700914 }
915
916 /**
917 * This function finds and forcibly binds to a random system port, ensuring that the port cannot
918 * be unbound.
919 *
920 * <p>A socket cannot be un-bound from a port if it was bound to that port by number. To select
921 * a random open port and then bind by number, this function creates a temp socket, binds to a
922 * random port (specifying 0), gets that port number, and then uses is to bind the user's UDP
923 * Encapsulation Socket forcibly, so that it cannot be un-bound by the user with the returned
924 * FileHandle.
925 *
926 * <p>The loop in this function handles the inherent race window between un-binding to a port
927 * and re-binding, during which the system could *technically* hand that port out to someone
928 * else.
929 */
Benedict Wongf186d672017-10-10 20:44:28 -0700930 private int bindToRandomPort(FileDescriptor sockFd) throws IOException {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700931 for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
932 try {
933 FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
934 Os.bind(probeSocket, INADDR_ANY, 0);
935 int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
936 Os.close(probeSocket);
937 Log.v(TAG, "Binding to port " + port);
938 Os.bind(sockFd, INADDR_ANY, port);
Benedict Wongf186d672017-10-10 20:44:28 -0700939 return port;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700940 } catch (ErrnoException e) {
941 // Someone miraculously claimed the port just after we closed probeSocket.
942 if (e.errno == OsConstants.EADDRINUSE) {
943 continue;
944 }
945 throw e.rethrowAsIOException();
946 }
947 }
948 throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
949 }
Nathan Harold93962f32017-03-07 13:23:36 -0800950
951 /**
Benedict Wongbabe5d72017-12-03 19:42:36 -0800952 * Functional interface to do traffic tagging of given sockets to UIDs.
953 *
954 * <p>Specifically used by openUdpEncapsulationSocket to ensure data usage on the UDP encap
955 * sockets are billed to the UID that the UDP encap socket was created on behalf of.
956 *
957 * <p>Separate class so that the socket tagging logic can be mocked; TrafficStats uses static
958 * methods that cannot be easily mocked/tested.
959 */
960 @VisibleForTesting
961 public interface UidFdTagger {
962 /**
963 * Sets socket tag to assign all traffic to the provided UID.
964 *
965 * <p>Since the socket is created on behalf of an unprivileged application, all traffic
966 * should be accounted to the UID of the unprivileged application.
967 */
968 public void tag(FileDescriptor fd, int uid) throws IOException;
969 }
970
971 /**
Nathan Harold93962f32017-03-07 13:23:36 -0800972 * Open a socket via the system server and bind it to the specified port (random if port=0).
973 * This will return a PFD to the user that represent a bound UDP socket. The system server will
974 * cache the socket and a record of its owner so that it can and must be freed when no longer
975 * needed.
976 */
977 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700978 public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
979 throws RemoteException {
980 if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) {
981 throw new IllegalArgumentException(
982 "Specified port number must be a valid non-reserved UDP port");
983 }
Nathan Harolda10003d2017-08-23 13:46:33 -0700984 checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket");
985
Benedict Wongbabe5d72017-12-03 19:42:36 -0800986 int callingUid = Binder.getCallingUid();
987 UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
Nathan Haroldd8c74292017-12-13 19:16:33 -0800988 final int resourceId = mNextResourceId++;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700989 FileDescriptor sockFd = null;
990 try {
Benedict Wong344bd622017-11-16 15:27:22 -0800991 if (!userRecord.mSocketQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700992 return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
993 }
994
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700995 sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
Benedict Wongbabe5d72017-12-03 19:42:36 -0800996 mUidFdTagger.tag(sockFd, callingUid);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700997
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700998 // This code is common to both the unspecified and specified port cases
999 Os.setsockoptInt(
1000 sockFd,
1001 OsConstants.IPPROTO_UDP,
1002 OsConstants.UDP_ENCAP,
1003 OsConstants.UDP_ENCAP_ESPINUDP);
1004
Benedict Wongba8d3132017-12-06 21:56:35 -08001005 mSrvConfig.getNetdInstance().ipSecSetEncapSocketOwner(sockFd, callingUid);
1006 if (port != 0) {
1007 Log.v(TAG, "Binding to port " + port);
1008 Os.bind(sockFd, INADDR_ANY, port);
1009 } else {
1010 port = bindToRandomPort(sockFd);
1011 }
1012
Benedict Wong344bd622017-11-16 15:27:22 -08001013 userRecord.mEncapSocketRecords.put(
1014 resourceId,
1015 new RefcountedResource<EncapSocketRecord>(
1016 new EncapSocketRecord(resourceId, sockFd, port), binder));
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001017 return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd);
1018 } catch (IOException | ErrnoException e) {
1019 IoUtils.closeQuietly(sockFd);
1020 }
1021 // If we make it to here, then something has gone wrong and we couldn't open a socket.
1022 // The only reasonable condition that would cause that is resource unavailable.
1023 return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
Nathan Harold93962f32017-03-07 13:23:36 -08001024 }
1025
1026 /** close a socket that has been been allocated by and registered with the system server */
1027 @Override
Benedict Wong344bd622017-11-16 15:27:22 -08001028 public synchronized void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {
1029 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1030 releaseResource(userRecord.mEncapSocketRecords, resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001031 }
Nathan Harold93962f32017-03-07 13:23:36 -08001032
1033 /**
Nathan Harolda10003d2017-08-23 13:46:33 -07001034 * Checks an IpSecConfig parcel to ensure that the contents are sane and throws an
1035 * IllegalArgumentException if they are not.
1036 */
1037 private void checkIpSecConfig(IpSecConfig config) {
Benedict Wong344bd622017-11-16 15:27:22 -08001038 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1039
Nathan Harolda10003d2017-08-23 13:46:33 -07001040 if (config.getLocalAddress() == null) {
1041 throw new IllegalArgumentException("Invalid null Local InetAddress");
1042 }
1043
1044 if (config.getRemoteAddress() == null) {
1045 throw new IllegalArgumentException("Invalid null Remote InetAddress");
1046 }
1047
1048 switch (config.getMode()) {
1049 case IpSecTransform.MODE_TRANSPORT:
1050 if (!config.getLocalAddress().isEmpty()) {
1051 throw new IllegalArgumentException("Non-empty Local Address");
1052 }
1053 // Must be valid, and not a wildcard
1054 checkInetAddress(config.getRemoteAddress());
1055 break;
1056 case IpSecTransform.MODE_TUNNEL:
1057 break;
1058 default:
1059 throw new IllegalArgumentException(
1060 "Invalid IpSecTransform.mode: " + config.getMode());
1061 }
1062
1063 switch (config.getEncapType()) {
1064 case IpSecTransform.ENCAP_NONE:
1065 break;
1066 case IpSecTransform.ENCAP_ESPINUDP:
1067 case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
Benedict Wong344bd622017-11-16 15:27:22 -08001068 // Retrieve encap socket record; will throw IllegalArgumentException if not found
1069 userRecord.mEncapSocketRecords.getResourceOrThrow(
1070 config.getEncapSocketResourceId());
Nathan Harolda10003d2017-08-23 13:46:33 -07001071
1072 int port = config.getEncapRemotePort();
1073 if (port <= 0 || port > 0xFFFF) {
1074 throw new IllegalArgumentException("Invalid remote UDP port: " + port);
1075 }
1076 break;
1077 default:
1078 throw new IllegalArgumentException("Invalid Encap Type: " + config.getEncapType());
1079 }
1080
1081 for (int direction : DIRECTIONS) {
1082 IpSecAlgorithm crypt = config.getEncryption(direction);
1083 IpSecAlgorithm auth = config.getAuthentication(direction);
Benedict Wong0febe5e2017-08-22 21:42:33 -07001084 IpSecAlgorithm authenticatedEncryption = config.getAuthenticatedEncryption(direction);
1085 if (authenticatedEncryption == null && crypt == null && auth == null) {
1086 throw new IllegalArgumentException(
1087 "No Encryption or Authentication algorithms specified");
1088 } else if (authenticatedEncryption != null && (auth != null || crypt != null)) {
1089 throw new IllegalArgumentException(
1090 "Authenticated Encryption is mutually"
1091 + " exclusive with other Authentication or Encryption algorithms");
Nathan Harolda10003d2017-08-23 13:46:33 -07001092 }
1093
Benedict Wong344bd622017-11-16 15:27:22 -08001094 // Retrieve SPI record; will throw IllegalArgumentException if not found
1095 userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId(direction));
Nathan Harolda10003d2017-08-23 13:46:33 -07001096 }
1097 }
1098
1099 /**
Nathan Harold93962f32017-03-07 13:23:36 -08001100 * Create a transport mode transform, which represent two security associations (one in each
1101 * direction) in the kernel. The transform will be cached by the system server and must be freed
1102 * when no longer needed. It is possible to free one, deleting the SA from underneath sockets
1103 * that are using it, which will result in all of those sockets becoming unable to send or
1104 * receive data.
1105 */
1106 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001107 public synchronized IpSecTransformResponse createTransportModeTransform(
1108 IpSecConfig c, IBinder binder) throws RemoteException {
Nathan Harolda10003d2017-08-23 13:46:33 -07001109 checkIpSecConfig(c);
1110 checkNotNull(binder, "Null Binder passed to createTransportModeTransform");
Nathan Haroldd8c74292017-12-13 19:16:33 -08001111 final int resourceId = mNextResourceId++;
Benedict Wong344bd622017-11-16 15:27:22 -08001112
1113 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1114
1115 // Avoid resizing by creating a dependency array of min-size 3 (1 UDP encap + 2 SPIs)
1116 List<RefcountedResource> dependencies = new ArrayList<>(3);
1117
1118 if (!userRecord.mTransformQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -07001119 return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
1120 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001121 SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
Nathan Harolda10003d2017-08-23 13:46:33 -07001122
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001123 int encapType, encapLocalPort = 0, encapRemotePort = 0;
Benedict Wong344bd622017-11-16 15:27:22 -08001124 EncapSocketRecord socketRecord = null;
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001125 encapType = c.getEncapType();
1126 if (encapType != IpSecTransform.ENCAP_NONE) {
Benedict Wong344bd622017-11-16 15:27:22 -08001127 RefcountedResource<EncapSocketRecord> refcountedSocketRecord =
1128 userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(
1129 c.getEncapSocketResourceId());
1130 dependencies.add(refcountedSocketRecord);
1131
1132 socketRecord = refcountedSocketRecord.getResource();
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001133 encapLocalPort = socketRecord.getPort();
1134 encapRemotePort = c.getEncapRemotePort();
1135 }
1136
Nathan Harold93962f32017-03-07 13:23:36 -08001137 for (int direction : DIRECTIONS) {
1138 IpSecAlgorithm auth = c.getAuthentication(direction);
1139 IpSecAlgorithm crypt = c.getEncryption(direction);
Benedict Wong0febe5e2017-08-22 21:42:33 -07001140 IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(direction);
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001141
Benedict Wong344bd622017-11-16 15:27:22 -08001142 RefcountedResource<SpiRecord> refcountedSpiRecord =
1143 userRecord.mSpiRecords.getRefcountedResourceOrThrow(
1144 c.getSpiResourceId(direction));
1145 dependencies.add(refcountedSpiRecord);
1146
1147 spis[direction] = refcountedSpiRecord.getResource();
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001148 int spi = spis[direction].getSpi();
Nathan Harold93962f32017-03-07 13:23:36 -08001149 try {
Nathan Harolda10003d2017-08-23 13:46:33 -07001150 mSrvConfig
1151 .getNetdInstance()
ludi0f807892017-05-20 14:15:09 -07001152 .ipSecAddSecurityAssociation(
1153 resourceId,
1154 c.getMode(),
1155 direction,
Nathan Harolda10003d2017-08-23 13:46:33 -07001156 c.getLocalAddress(),
1157 c.getRemoteAddress(),
1158 (c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0,
ludi0f807892017-05-20 14:15:09 -07001159 spi,
1160 (auth != null) ? auth.getName() : "",
Manoj Boopathi Rajfffa8112017-10-26 11:49:02 -07001161 (auth != null) ? auth.getKey() : new byte[] {},
ludi0f807892017-05-20 14:15:09 -07001162 (auth != null) ? auth.getTruncationLengthBits() : 0,
1163 (crypt != null) ? crypt.getName() : "",
Manoj Boopathi Rajfffa8112017-10-26 11:49:02 -07001164 (crypt != null) ? crypt.getKey() : new byte[] {},
ludi0f807892017-05-20 14:15:09 -07001165 (crypt != null) ? crypt.getTruncationLengthBits() : 0,
Benedict Wong0febe5e2017-08-22 21:42:33 -07001166 (authCrypt != null) ? authCrypt.getName() : "",
Manoj Boopathi Rajfffa8112017-10-26 11:49:02 -07001167 (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
Benedict Wong0febe5e2017-08-22 21:42:33 -07001168 (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
ludi0f807892017-05-20 14:15:09 -07001169 encapType,
1170 encapLocalPort,
1171 encapRemotePort);
Nathan Harold93962f32017-03-07 13:23:36 -08001172 } catch (ServiceSpecificException e) {
1173 // FIXME: get the error code and throw is at an IOException from Errno Exception
ludi0f807892017-05-20 14:15:09 -07001174 return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
Nathan Harold93962f32017-03-07 13:23:36 -08001175 }
1176 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001177 // Both SAs were created successfully, time to construct a record and lock it away
Benedict Wong344bd622017-11-16 15:27:22 -08001178 userRecord.mTransformRecords.put(
1179 resourceId,
1180 new RefcountedResource<TransformRecord>(
1181 new TransformRecord(resourceId, c, spis, socketRecord),
1182 binder,
1183 dependencies.toArray(new RefcountedResource[dependencies.size()])));
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001184 return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001185 }
1186
1187 /**
1188 * Delete a transport mode transform that was previously allocated by + registered with the
1189 * system server. If this is called on an inactive (or non-existent) transform, it will not
1190 * return an error. It's safe to de-allocate transforms that may have already been deleted for
1191 * other reasons.
1192 */
1193 @Override
Benedict Wong344bd622017-11-16 15:27:22 -08001194 public synchronized void deleteTransportModeTransform(int resourceId) throws RemoteException {
1195 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1196 releaseResource(userRecord.mTransformRecords, resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001197 }
1198
1199 /**
1200 * Apply an active transport mode transform to a socket, which will apply the IPsec security
1201 * association as a correspondent policy to the provided socket
1202 */
1203 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001204 public synchronized void applyTransportModeTransform(
1205 ParcelFileDescriptor socket, int resourceId) throws RemoteException {
Benedict Wong344bd622017-11-16 15:27:22 -08001206 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
Nathan Harold93962f32017-03-07 13:23:36 -08001207
Benedict Wong344bd622017-11-16 15:27:22 -08001208 // Get transform record; if no transform is found, will throw IllegalArgumentException
1209 TransformRecord info = userRecord.mTransformRecords.getResourceOrThrow(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001210
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001211 // TODO: make this a function.
1212 if (info.pid != getCallingPid() || info.uid != getCallingUid()) {
1213 throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
1214 }
1215
1216 IpSecConfig c = info.getConfig();
1217 try {
1218 for (int direction : DIRECTIONS) {
ludi1a06aa72017-05-12 09:15:00 -07001219 mSrvConfig
1220 .getNetdInstance()
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001221 .ipSecApplyTransportModeTransform(
1222 socket.getFileDescriptor(),
1223 resourceId,
1224 direction,
Nathan Harolda10003d2017-08-23 13:46:33 -07001225 c.getLocalAddress(),
1226 c.getRemoteAddress(),
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001227 info.getSpiRecord(direction).getSpi());
Nathan Harold93962f32017-03-07 13:23:36 -08001228 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001229 } catch (ServiceSpecificException e) {
manojboopathic4be79d2017-11-30 17:11:49 -08001230 if (e.errorCode == EINVAL) {
1231 throw new IllegalArgumentException(e.toString());
1232 } else {
1233 throw e;
1234 }
Nathan Harold93962f32017-03-07 13:23:36 -08001235 }
1236 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001237
Nathan Harold93962f32017-03-07 13:23:36 -08001238 /**
1239 * Remove a transport mode transform from a socket, applying the default (empty) policy. This
1240 * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of
1241 * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not
1242 * used: reserved for future improved input validation.
1243 */
1244 @Override
Benedict Wong344bd622017-11-16 15:27:22 -08001245 public synchronized void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
Nathan Harold93962f32017-03-07 13:23:36 -08001246 throws RemoteException {
1247 try {
ludi1a06aa72017-05-12 09:15:00 -07001248 mSrvConfig
1249 .getNetdInstance()
1250 .ipSecRemoveTransportModeTransform(socket.getFileDescriptor());
Nathan Harold93962f32017-03-07 13:23:36 -08001251 } catch (ServiceSpecificException e) {
1252 // FIXME: get the error code and throw is at an IOException from Errno Exception
1253 }
1254 }
1255
1256 @Override
ludib0c95b12017-05-22 10:52:23 -07001257 protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Nathan Harold1afbef42017-03-01 18:55:06 -08001258 mContext.enforceCallingOrSelfPermission(DUMP, TAG);
ludib0c95b12017-05-22 10:52:23 -07001259
1260 pw.println("IpSecService dump:");
Nathan Harold1afbef42017-03-01 18:55:06 -08001261 pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
1262 pw.println();
ludib0c95b12017-05-22 10:52:23 -07001263
Benedict Wong344bd622017-11-16 15:27:22 -08001264 pw.println("mUserResourceTracker:");
1265 pw.println(mUserResourceTracker);
Nathan Harold1afbef42017-03-01 18:55:06 -08001266 }
1267}