blob: d3ab1259c9ed5d5d4b97ee187c6da2aa986b37d1 [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;
Benedict Wongbabe5d72017-12-03 19:42:36 -080037import android.net.TrafficStats;
Nathan Harold1afbef42017-03-01 18:55:06 -080038import android.net.util.NetdService;
Nathan Harold93962f32017-03-07 13:23:36 -080039import android.os.Binder;
Nathan Harold93962f32017-03-07 13:23:36 -080040import android.os.IBinder;
41import android.os.ParcelFileDescriptor;
Nathan Harold1afbef42017-03-01 18:55:06 -080042import android.os.RemoteException;
Nathan Harold93962f32017-03-07 13:23:36 -080043import android.os.ServiceSpecificException;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070044import android.system.ErrnoException;
45import android.system.Os;
46import android.system.OsConstants;
Nathan Harolda10003d2017-08-23 13:46:33 -070047import android.text.TextUtils;
Nathan Harold1afbef42017-03-01 18:55:06 -080048import android.util.Log;
49import android.util.Slog;
Nathan Harold93962f32017-03-07 13:23:36 -080050import android.util.SparseArray;
Nathan Harolda10003d2017-08-23 13:46:33 -070051
Nathan Harold93962f32017-03-07 13:23:36 -080052import com.android.internal.annotations.GuardedBy;
ludi1a06aa72017-05-12 09:15:00 -070053import com.android.internal.annotations.VisibleForTesting;
Nathan Harolda10003d2017-08-23 13:46:33 -070054
Nathan Harold1afbef42017-03-01 18:55:06 -080055import java.io.FileDescriptor;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070056import java.io.IOException;
Nathan Harold1afbef42017-03-01 18:55:06 -080057import java.io.PrintWriter;
Nathan Harold8dc1fd02017-04-04 19:37:48 -070058import java.net.InetAddress;
59import java.net.InetSocketAddress;
60import java.net.UnknownHostException;
Benedict Wong409c8ca2017-10-26 19:41:43 -070061import java.util.ArrayList;
62import java.util.List;
Nathan Harold93962f32017-03-07 13:23:36 -080063import java.util.concurrent.atomic.AtomicInteger;
Nathan Harolda10003d2017-08-23 13:46:33 -070064
Nathan Harold8dc1fd02017-04-04 19:37:48 -070065import libcore.io.IoUtils;
Nathan Harold1afbef42017-03-01 18:55:06 -080066
Benedict Wong409c8ca2017-10-26 19:41:43 -070067/**
68 * A service to manage multiple clients that want to access the IpSec API. The service is
69 * responsible for maintaining a list of clients and managing the resources (and related quotas)
70 * that each of them own.
71 *
72 * <p>Synchronization in IpSecService is done on all entrypoints due to potential race conditions at
73 * the kernel/xfrm level. Further, this allows the simplifying assumption to be made that only one
74 * thread is ever running at a time.
75 *
76 * @hide
77 */
Nathan Harold1afbef42017-03-01 18:55:06 -080078public class IpSecService extends IIpSecService.Stub {
79 private static final String TAG = "IpSecService";
80 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
Nathan Harold8dc1fd02017-04-04 19:37:48 -070081
Nathan Harold1afbef42017-03-01 18:55:06 -080082 private static final String NETD_SERVICE_NAME = "netd";
Nathan Harold93962f32017-03-07 13:23:36 -080083 private static final int[] DIRECTIONS =
84 new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN};
Nathan Harold1afbef42017-03-01 18:55:06 -080085
ludi1a06aa72017-05-12 09:15:00 -070086 private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms
Nathan Harold8dc1fd02017-04-04 19:37:48 -070087 private static final int MAX_PORT_BIND_ATTEMPTS = 10;
88 private static final InetAddress INADDR_ANY;
89
90 static {
91 try {
92 INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
93 } catch (UnknownHostException e) {
94 throw new RuntimeException(e);
95 }
96 }
97
98 static final int FREE_PORT_MIN = 1024; // ports 1-1023 are reserved
99 static final int PORT_MAX = 0xFFFF; // ports are an unsigned 16-bit integer
100
101 /* Binder context for this service */
Nathan Harold1afbef42017-03-01 18:55:06 -0800102 private final Context mContext;
103
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700104 /** Should be a never-repeating global ID for resources */
105 private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0);
Nathan Harold1afbef42017-03-01 18:55:06 -0800106
ludi1a06aa72017-05-12 09:15:00 -0700107 interface IpSecServiceConfiguration {
108 INetd getNetdInstance() throws RemoteException;
109
110 static IpSecServiceConfiguration GETSRVINSTANCE =
111 new IpSecServiceConfiguration() {
112 @Override
113 public INetd getNetdInstance() throws RemoteException {
114 final INetd netd = NetdService.getInstance();
115 if (netd == null) {
116 throw new RemoteException("Failed to Get Netd Instance");
117 }
118 return netd;
119 }
120 };
121 }
122
123 private final IpSecServiceConfiguration mSrvConfig;
Benedict Wongbabe5d72017-12-03 19:42:36 -0800124 final UidFdTagger mUidFdTagger;
ludi1a06aa72017-05-12 09:15:00 -0700125
Benedict Wong409c8ca2017-10-26 19:41:43 -0700126 /**
127 * Interface for user-reference and kernel-resource cleanup.
128 *
129 * <p>This interface must be implemented for a resource to be reference counted.
130 */
131 @VisibleForTesting
132 public interface IResource {
133 /**
134 * Invalidates a IResource object, ensuring it is invalid for the purposes of allocating new
135 * objects dependent on it.
136 *
137 * <p>Implementations of this method are expected to remove references to the IResource
138 * object from the IpSecService's tracking arrays. The removal from the arrays ensures that
139 * the resource is considered invalid for user access or allocation or use in other
140 * resources.
141 *
142 * <p>References to the IResource object may be held by other RefcountedResource objects,
143 * and as such, the kernel resources and quota may not be cleaned up.
144 */
145 void invalidate() throws RemoteException;
146
147 /**
148 * Releases underlying resources and related quotas.
149 *
150 * <p>Implementations of this method are expected to remove all system resources that are
151 * tracked by the IResource object. Due to other RefcountedResource objects potentially
Benedict Wong344bd622017-11-16 15:27:22 -0800152 * having references to the IResource object, freeUnderlyingResources may not always be
Benedict Wong409c8ca2017-10-26 19:41:43 -0700153 * called from releaseIfUnreferencedRecursively().
154 */
155 void freeUnderlyingResources() throws RemoteException;
156 }
157
158 /**
159 * RefcountedResource manages references and dependencies in an exclusively acyclic graph.
160 *
161 * <p>RefcountedResource implements both explicit and implicit resource management. Creating a
162 * RefcountedResource object creates an explicit reference that must be freed by calling
163 * userRelease(). Additionally, adding this object as a child of another RefcountedResource
164 * object will add an implicit reference.
165 *
166 * <p>Resources are cleaned up when all references, both implicit and explicit, are released
167 * (ie, when userRelease() is called and when all parents have called releaseReference() on this
168 * object.)
169 */
170 @VisibleForTesting
171 public class RefcountedResource<T extends IResource> implements IBinder.DeathRecipient {
172 private final T mResource;
173 private final List<RefcountedResource> mChildren;
174 int mRefCount = 1; // starts at 1 for user's reference.
175 IBinder mBinder;
176
177 RefcountedResource(T resource, IBinder binder, RefcountedResource... children) {
178 synchronized (IpSecService.this) {
179 this.mResource = resource;
180 this.mChildren = new ArrayList<>(children.length);
181 this.mBinder = binder;
182
183 for (RefcountedResource child : children) {
184 mChildren.add(child);
185 child.mRefCount++;
186 }
187
188 try {
189 mBinder.linkToDeath(this, 0);
190 } catch (RemoteException e) {
191 binderDied();
192 }
193 }
194 }
195
196 /**
197 * If the Binder object dies, this function is called to free the system resources that are
Benedict Wong344bd622017-11-16 15:27:22 -0800198 * being tracked by this record and to subsequently release this record for garbage
Benedict Wong409c8ca2017-10-26 19:41:43 -0700199 * collection
200 */
201 @Override
202 public void binderDied() {
203 synchronized (IpSecService.this) {
204 try {
205 userRelease();
206 } catch (Exception e) {
207 Log.e(TAG, "Failed to release resource: " + e);
208 }
209 }
210 }
211
212 public T getResource() {
213 return mResource;
214 }
215
216 /**
217 * Unlinks from binder and performs IpSecService resource cleanup (removes from resource
218 * arrays)
219 *
220 * <p>If this method has been previously called, the RefcountedResource's binder field will
221 * be null, and the method will return without performing the cleanup a second time.
222 *
223 * <p>Note that calling this function does not imply that kernel resources will be freed at
224 * this time, or that the related quota will be returned. Such actions will only be
225 * performed upon the reference count reaching zero.
226 */
227 @GuardedBy("IpSecService.this")
228 public void userRelease() throws RemoteException {
229 // Prevent users from putting reference counts into a bad state by calling
230 // userRelease() multiple times.
231 if (mBinder == null) {
232 return;
233 }
234
235 mBinder.unlinkToDeath(this, 0);
236 mBinder = null;
237
238 mResource.invalidate();
239
240 releaseReference();
241 }
242
243 /**
244 * Removes a reference to this resource. If the resultant reference count is zero, the
245 * underlying resources are freed, and references to all child resources are also dropped
246 * recursively (resulting in them freeing their resources and children, etcetera)
247 *
248 * <p>This method also sets the reference count to an invalid value (-1) to signify that it
249 * has been fully released. Any subsequent calls to this method will result in an
250 * IllegalStateException being thrown due to resource already having been previously
251 * released
252 */
253 @VisibleForTesting
254 @GuardedBy("IpSecService.this")
255 public void releaseReference() throws RemoteException {
256 mRefCount--;
257
258 if (mRefCount > 0) {
259 return;
260 } else if (mRefCount < 0) {
261 throw new IllegalStateException(
262 "Invalid operation - resource has already been released.");
263 }
264
265 // Cleanup own resources
266 mResource.freeUnderlyingResources();
267
268 // Cleanup child resources as needed
269 for (RefcountedResource<? extends IResource> child : mChildren) {
270 child.releaseReference();
271 }
272
273 // Enforce that resource cleanup can only be called once
274 // By decrementing the refcount (from 0 to -1), the next call will throw an
275 // IllegalStateException - it has already been released fully.
276 mRefCount--;
277 }
278
279 @Override
280 public String toString() {
281 return new StringBuilder()
282 .append("{mResource=")
283 .append(mResource)
284 .append(", mRefCount=")
285 .append(mRefCount)
286 .append(", mChildren=")
287 .append(mChildren)
288 .append("}")
289 .toString();
290 }
291 }
292
Nathan Harolda1afbd82017-04-24 16:16:34 -0700293 /* Very simple counting class that looks much like a counting semaphore */
Benedict Wong344bd622017-11-16 15:27:22 -0800294 @VisibleForTesting
295 static class ResourceTracker {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700296 private final int mMax;
297 int mCurrent;
298
299 ResourceTracker(int max) {
300 mMax = max;
301 mCurrent = 0;
302 }
303
Benedict Wong344bd622017-11-16 15:27:22 -0800304 boolean isAvailable() {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700305 return (mCurrent < mMax);
306 }
307
Benedict Wong344bd622017-11-16 15:27:22 -0800308 void take() {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700309 if (!isAvailable()) {
310 Log.wtf(TAG, "Too many resources allocated!");
311 }
312 mCurrent++;
313 }
314
Benedict Wong344bd622017-11-16 15:27:22 -0800315 void give() {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700316 if (mCurrent <= 0) {
317 Log.wtf(TAG, "We've released this resource too many times");
318 }
319 mCurrent--;
320 }
ludi3e5ea232017-08-10 15:44:40 -0700321
322 @Override
323 public String toString() {
324 return new StringBuilder()
325 .append("{mCurrent=")
326 .append(mCurrent)
327 .append(", mMax=")
328 .append(mMax)
329 .append("}")
330 .toString();
331 }
Nathan Harolda1afbd82017-04-24 16:16:34 -0700332 }
333
Benedict Wong344bd622017-11-16 15:27:22 -0800334 @VisibleForTesting
335 static final class UserRecord {
336 /* Type names */
337 public static final String TYPENAME_SPI = "SecurityParameterIndex";
338 public static final String TYPENAME_TRANSFORM = "IpSecTransform";
339 public static final String TYPENAME_ENCAP_SOCKET = "UdpEncapSocket";
340
341 /* Maximum number of each type of resource that a single UID may possess */
Nathan Harolda1afbd82017-04-24 16:16:34 -0700342 public static final int MAX_NUM_ENCAP_SOCKETS = 2;
Nathan Harolda1afbd82017-04-24 16:16:34 -0700343 public static final int MAX_NUM_TRANSFORMS = 4;
Nathan Harolda1afbd82017-04-24 16:16:34 -0700344 public static final int MAX_NUM_SPIS = 8;
345
Benedict Wong344bd622017-11-16 15:27:22 -0800346 final RefcountedResourceArray<SpiRecord> mSpiRecords =
347 new RefcountedResourceArray<>(TYPENAME_SPI);
348 final ResourceTracker mSpiQuotaTracker = new ResourceTracker(MAX_NUM_SPIS);
ludi3e5ea232017-08-10 15:44:40 -0700349
Benedict Wong344bd622017-11-16 15:27:22 -0800350 final RefcountedResourceArray<TransformRecord> mTransformRecords =
351 new RefcountedResourceArray<>(TYPENAME_TRANSFORM);
352 final ResourceTracker mTransformQuotaTracker = new ResourceTracker(MAX_NUM_TRANSFORMS);
353
354 final RefcountedResourceArray<EncapSocketRecord> mEncapSocketRecords =
355 new RefcountedResourceArray<>(TYPENAME_ENCAP_SOCKET);
356 final ResourceTracker mSocketQuotaTracker = new ResourceTracker(MAX_NUM_ENCAP_SOCKETS);
357
358 void removeSpiRecord(int resourceId) {
359 mSpiRecords.remove(resourceId);
Nathan Harolda1afbd82017-04-24 16:16:34 -0700360 }
361
Benedict Wong344bd622017-11-16 15:27:22 -0800362 void removeTransformRecord(int resourceId) {
363 mTransformRecords.remove(resourceId);
364 }
365
366 void removeEncapSocketRecord(int resourceId) {
367 mEncapSocketRecords.remove(resourceId);
368 }
369
370 @Override
371 public String toString() {
372 return new StringBuilder()
373 .append("{mSpiQuotaTracker=")
374 .append(mSpiQuotaTracker)
375 .append(", mTransformQuotaTracker=")
376 .append(mTransformQuotaTracker)
377 .append(", mSocketQuotaTracker=")
378 .append(mSocketQuotaTracker)
379 .append(", mSpiRecords=")
380 .append(mSpiRecords)
381 .append(", mTransformRecords=")
382 .append(mTransformRecords)
383 .append(", mEncapSocketRecords=")
384 .append(mEncapSocketRecords)
385 .append("}")
386 .toString();
387 }
388 }
389
390 @VisibleForTesting
391 static final class UserResourceTracker {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700392 private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
393
Benedict Wong344bd622017-11-16 15:27:22 -0800394 /** Never-fail getter that populates the list of UIDs as-needed */
395 public UserRecord getUserRecord(int uid) {
396 checkCallerUid(uid);
397
Nathan Harolda1afbd82017-04-24 16:16:34 -0700398 UserRecord r = mUserRecords.get(uid);
399 if (r == null) {
400 r = new UserRecord();
401 mUserRecords.put(uid, r);
402 }
403 return r;
404 }
ludi3e5ea232017-08-10 15:44:40 -0700405
Benedict Wong344bd622017-11-16 15:27:22 -0800406 /** Safety method; guards against access of other user's UserRecords */
407 private void checkCallerUid(int uid) {
408 if (uid != Binder.getCallingUid()
409 && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
410 throw new SecurityException("Attempted access of unowned resources");
411 }
412 }
413
ludi3e5ea232017-08-10 15:44:40 -0700414 @Override
415 public String toString() {
416 return mUserRecords.toString();
417 }
Nathan Harolda1afbd82017-04-24 16:16:34 -0700418 }
419
Benedict Wong344bd622017-11-16 15:27:22 -0800420 @VisibleForTesting final UserResourceTracker mUserResourceTracker = new UserResourceTracker();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700421
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700422 /**
Benedict Wong344bd622017-11-16 15:27:22 -0800423 * The KernelResourceRecord class provides a facility to cleanly and reliably track system
424 * resources. It relies on a provided resourceId that should uniquely identify the kernel
425 * resource. To use this class, the user should implement the invalidate() and
426 * freeUnderlyingResources() methods that are responsible for cleaning up IpSecService resource
427 * tracking arrays and kernel resources, respectively
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700428 */
Benedict Wong344bd622017-11-16 15:27:22 -0800429 private abstract class KernelResourceRecord implements IResource {
Nathan Harold93962f32017-03-07 13:23:36 -0800430 final int pid;
431 final int uid;
Benedict Wong344bd622017-11-16 15:27:22 -0800432 protected final int mResourceId;
Nathan Harold93962f32017-03-07 13:23:36 -0800433
Benedict Wong344bd622017-11-16 15:27:22 -0800434 KernelResourceRecord(int resourceId) {
Nathan Harold93962f32017-03-07 13:23:36 -0800435 super();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700436 if (resourceId == INVALID_RESOURCE_ID) {
437 throw new IllegalArgumentException("Resource ID must not be INVALID_RESOURCE_ID");
438 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700439 mResourceId = resourceId;
Nathan Harold93962f32017-03-07 13:23:36 -0800440 pid = Binder.getCallingPid();
441 uid = Binder.getCallingUid();
442
Nathan Harolda1afbd82017-04-24 16:16:34 -0700443 getResourceTracker().take();
Nathan Harold93962f32017-03-07 13:23:36 -0800444 }
445
Benedict Wong344bd622017-11-16 15:27:22 -0800446 @Override
447 public abstract void invalidate() throws RemoteException;
448
449 /** Convenience method; retrieves the user resource record for the stored UID. */
450 protected UserRecord getUserRecord() {
451 return mUserResourceTracker.getUserRecord(uid);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700452 }
453
Benedict Wong344bd622017-11-16 15:27:22 -0800454 @Override
455 public abstract void freeUnderlyingResources() throws RemoteException;
ludib0c95b12017-05-22 10:52:23 -0700456
Nathan Harolda1afbd82017-04-24 16:16:34 -0700457 /** Get the resource tracker for this resource */
458 protected abstract ResourceTracker getResourceTracker();
459
ludib0c95b12017-05-22 10:52:23 -0700460 @Override
461 public String toString() {
462 return new StringBuilder()
463 .append("{mResourceId=")
464 .append(mResourceId)
465 .append(", pid=")
466 .append(pid)
467 .append(", uid=")
468 .append(uid)
ludib0c95b12017-05-22 10:52:23 -0700469 .append("}")
470 .toString();
471 }
Nathan Harold93962f32017-03-07 13:23:36 -0800472 };
473
Benedict Wong344bd622017-11-16 15:27:22 -0800474 // TODO: Move this to right after RefcountedResource. With this here, Gerrit was showing many
475 // more things as changed.
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700476 /**
Benedict Wong344bd622017-11-16 15:27:22 -0800477 * Thin wrapper over SparseArray to ensure resources exist, and simplify generic typing.
478 *
479 * <p>RefcountedResourceArray prevents null insertions, and throws an IllegalArgumentException
480 * if a key is not found during a retrieval process.
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700481 */
Benedict Wong344bd622017-11-16 15:27:22 -0800482 static class RefcountedResourceArray<T extends IResource> {
483 SparseArray<RefcountedResource<T>> mArray = new SparseArray<>();
484 private final String mTypeName;
Nathan Harold93962f32017-03-07 13:23:36 -0800485
Benedict Wong344bd622017-11-16 15:27:22 -0800486 public RefcountedResourceArray(String typeName) {
487 this.mTypeName = typeName;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700488 }
489
Benedict Wong344bd622017-11-16 15:27:22 -0800490 /**
491 * Accessor method to get inner resource object.
492 *
493 * @throws IllegalArgumentException if no resource with provided key is found.
494 */
495 T getResourceOrThrow(int key) {
496 return getRefcountedResourceOrThrow(key).getResource();
497 }
498
499 /**
500 * Accessor method to get reference counting wrapper.
501 *
502 * @throws IllegalArgumentException if no resource with provided key is found.
503 */
504 RefcountedResource<T> getRefcountedResourceOrThrow(int key) {
505 RefcountedResource<T> resource = mArray.get(key);
506 if (resource == null) {
507 throw new IllegalArgumentException(
508 String.format("No such %s found for given id: %d", mTypeName, key));
509 }
510
511 return resource;
512 }
513
514 void put(int key, RefcountedResource<T> obj) {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700515 checkNotNull(obj, "Null resources cannot be added");
516 mArray.put(key, obj);
517 }
518
519 void remove(int key) {
520 mArray.remove(key);
521 }
ludib0c95b12017-05-22 10:52:23 -0700522
523 @Override
524 public String toString() {
525 return mArray.toString();
526 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700527 }
528
Benedict Wong344bd622017-11-16 15:27:22 -0800529 private final class TransformRecord extends KernelResourceRecord {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700530 private final IpSecConfig mConfig;
531 private final SpiRecord[] mSpis;
Benedict Wong344bd622017-11-16 15:27:22 -0800532 private final EncapSocketRecord mSocket;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700533
534 TransformRecord(
Benedict Wong344bd622017-11-16 15:27:22 -0800535 int resourceId, IpSecConfig config, SpiRecord[] spis, EncapSocketRecord socket) {
536 super(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800537 mConfig = config;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700538 mSpis = spis;
539 mSocket = socket;
Nathan Harold93962f32017-03-07 13:23:36 -0800540 }
541
542 public IpSecConfig getConfig() {
543 return mConfig;
544 }
545
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700546 public SpiRecord getSpiRecord(int direction) {
547 return mSpis[direction];
548 }
549
550 /** always guarded by IpSecService#this */
Nathan Harold93962f32017-03-07 13:23:36 -0800551 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800552 public void freeUnderlyingResources() {
Nathan Harold93962f32017-03-07 13:23:36 -0800553 for (int direction : DIRECTIONS) {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700554 int spi = mSpis[direction].getSpi();
Nathan Harold93962f32017-03-07 13:23:36 -0800555 try {
ludi1a06aa72017-05-12 09:15:00 -0700556 mSrvConfig
557 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800558 .ipSecDeleteSecurityAssociation(
559 mResourceId,
560 direction,
Nathan Harolda10003d2017-08-23 13:46:33 -0700561 mConfig.getLocalAddress(),
562 mConfig.getRemoteAddress(),
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700563 spi);
Nathan Harold93962f32017-03-07 13:23:36 -0800564 } catch (ServiceSpecificException e) {
565 // FIXME: get the error code and throw is at an IOException from Errno Exception
566 } catch (RemoteException e) {
567 Log.e(TAG, "Failed to delete SA with ID: " + mResourceId);
568 }
569 }
Nathan Harold93962f32017-03-07 13:23:36 -0800570
Benedict Wong344bd622017-11-16 15:27:22 -0800571 getResourceTracker().give();
Nathan Harold93962f32017-03-07 13:23:36 -0800572 }
ludib0c95b12017-05-22 10:52:23 -0700573
Benedict Wong344bd622017-11-16 15:27:22 -0800574 @Override
575 public void invalidate() throws RemoteException {
576 getUserRecord().removeTransformRecord(mResourceId);
577 }
578
579 @Override
Nathan Harolda1afbd82017-04-24 16:16:34 -0700580 protected ResourceTracker getResourceTracker() {
Benedict Wong344bd622017-11-16 15:27:22 -0800581 return getUserRecord().mTransformQuotaTracker;
Nathan Harolda1afbd82017-04-24 16:16:34 -0700582 }
583
ludib0c95b12017-05-22 10:52:23 -0700584 @Override
585 public String toString() {
586 StringBuilder strBuilder = new StringBuilder();
587 strBuilder
588 .append("{super=")
589 .append(super.toString())
590 .append(", mSocket=")
591 .append(mSocket)
592 .append(", mSpis[OUT].mResourceId=")
593 .append(mSpis[IpSecTransform.DIRECTION_OUT].mResourceId)
594 .append(", mSpis[IN].mResourceId=")
595 .append(mSpis[IpSecTransform.DIRECTION_IN].mResourceId)
596 .append(", mConfig=")
597 .append(mConfig)
598 .append("}");
599 return strBuilder.toString();
600 }
Nathan Harold93962f32017-03-07 13:23:36 -0800601 }
602
Benedict Wong344bd622017-11-16 15:27:22 -0800603 private final class SpiRecord extends KernelResourceRecord {
Nathan Harold93962f32017-03-07 13:23:36 -0800604 private final int mDirection;
605 private final String mLocalAddress;
606 private final String mRemoteAddress;
Nathan Harold93962f32017-03-07 13:23:36 -0800607 private int mSpi;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700608
609 private boolean mOwnedByTransform = false;
Nathan Harold93962f32017-03-07 13:23:36 -0800610
611 SpiRecord(
612 int resourceId,
613 int direction,
614 String localAddress,
615 String remoteAddress,
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700616 int spi) {
Benedict Wong344bd622017-11-16 15:27:22 -0800617 super(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -0800618 mDirection = direction;
619 mLocalAddress = localAddress;
620 mRemoteAddress = remoteAddress;
621 mSpi = spi;
Nathan Harold93962f32017-03-07 13:23:36 -0800622 }
623
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700624 /** always guarded by IpSecService#this */
625 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800626 public void freeUnderlyingResources() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700627 if (mOwnedByTransform) {
628 Log.d(TAG, "Cannot release Spi " + mSpi + ": Currently locked by a Transform");
629 // Because SPIs are "handed off" to transform, objects, they should never be
630 // freed from the SpiRecord once used in a transform. (They refer to the same SA,
631 // thus ownership and responsibility for freeing these resources passes to the
632 // Transform object). Thus, we should let the user free them without penalty once
633 // they are applied in a Transform object.
634 return;
635 }
636
Nathan Harold93962f32017-03-07 13:23:36 -0800637 try {
ludi1a06aa72017-05-12 09:15:00 -0700638 mSrvConfig
639 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800640 .ipSecDeleteSecurityAssociation(
641 mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi);
642 } catch (ServiceSpecificException e) {
643 // FIXME: get the error code and throw is at an IOException from Errno Exception
644 } catch (RemoteException e) {
645 Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId);
646 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700647
648 mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
Nathan Harold93962f32017-03-07 13:23:36 -0800649
Benedict Wong344bd622017-11-16 15:27:22 -0800650 getResourceTracker().give();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700651 }
652
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700653 public int getSpi() {
654 return mSpi;
655 }
656
657 public void setOwnedByTransform() {
658 if (mOwnedByTransform) {
659 // Programming error
Andreas Gamped6d8e452017-07-11 10:25:09 -0700660 throw new IllegalStateException("Cannot own an SPI twice!");
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700661 }
662
663 mOwnedByTransform = true;
Nathan Harold93962f32017-03-07 13:23:36 -0800664 }
ludib0c95b12017-05-22 10:52:23 -0700665
666 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800667 public void invalidate() throws RemoteException {
668 getUserRecord().removeSpiRecord(mResourceId);
669 }
670
671 @Override
672 protected ResourceTracker getResourceTracker() {
673 return getUserRecord().mSpiQuotaTracker;
674 }
675
676 @Override
ludib0c95b12017-05-22 10:52:23 -0700677 public String toString() {
678 StringBuilder strBuilder = new StringBuilder();
679 strBuilder
680 .append("{super=")
681 .append(super.toString())
682 .append(", mSpi=")
683 .append(mSpi)
684 .append(", mDirection=")
685 .append(mDirection)
686 .append(", mLocalAddress=")
687 .append(mLocalAddress)
688 .append(", mRemoteAddress=")
689 .append(mRemoteAddress)
690 .append(", mOwnedByTransform=")
691 .append(mOwnedByTransform)
692 .append("}");
693 return strBuilder.toString();
694 }
Nathan Harold93962f32017-03-07 13:23:36 -0800695 }
696
Benedict Wong344bd622017-11-16 15:27:22 -0800697 private final class EncapSocketRecord extends KernelResourceRecord {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700698 private FileDescriptor mSocket;
699 private final int mPort;
Nathan Harold93962f32017-03-07 13:23:36 -0800700
Benedict Wong344bd622017-11-16 15:27:22 -0800701 EncapSocketRecord(int resourceId, FileDescriptor socket, int port) {
702 super(resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700703 mSocket = socket;
704 mPort = port;
705 }
706
707 /** always guarded by IpSecService#this */
708 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800709 public void freeUnderlyingResources() {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700710 Log.d(TAG, "Closing port " + mPort);
711 IoUtils.closeQuietly(mSocket);
712 mSocket = null;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700713
Benedict Wong344bd622017-11-16 15:27:22 -0800714 getResourceTracker().give();
Nathan Harolda1afbd82017-04-24 16:16:34 -0700715 }
716
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700717 public int getPort() {
718 return mPort;
719 }
720
721 public FileDescriptor getSocket() {
722 return mSocket;
723 }
ludib0c95b12017-05-22 10:52:23 -0700724
725 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800726 protected ResourceTracker getResourceTracker() {
727 return getUserRecord().mSocketQuotaTracker;
728 }
729
730 @Override
731 public void invalidate() {
732 getUserRecord().removeEncapSocketRecord(mResourceId);
733 }
734
735 @Override
ludib0c95b12017-05-22 10:52:23 -0700736 public String toString() {
737 return new StringBuilder()
738 .append("{super=")
739 .append(super.toString())
740 .append(", mSocket=")
741 .append(mSocket)
742 .append(", mPort=")
743 .append(mPort)
744 .append("}")
745 .toString();
746 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700747 }
Nathan Harold93962f32017-03-07 13:23:36 -0800748
Nathan Harold1afbef42017-03-01 18:55:06 -0800749 /**
750 * Constructs a new IpSecService instance
751 *
752 * @param context Binder context for this service
753 */
754 private IpSecService(Context context) {
ludi1a06aa72017-05-12 09:15:00 -0700755 this(context, IpSecServiceConfiguration.GETSRVINSTANCE);
Nathan Harold1afbef42017-03-01 18:55:06 -0800756 }
757
758 static IpSecService create(Context context) throws InterruptedException {
759 final IpSecService service = new IpSecService(context);
760 service.connectNativeNetdService();
761 return service;
762 }
763
ludi1a06aa72017-05-12 09:15:00 -0700764 /** @hide */
765 @VisibleForTesting
766 public IpSecService(Context context, IpSecServiceConfiguration config) {
Benedict Wongbabe5d72017-12-03 19:42:36 -0800767 this(context, config, (fd, uid) -> {
768 try{
769 TrafficStats.setThreadStatsUid(uid);
770 TrafficStats.tagFileDescriptor(fd);
771 } finally {
772 TrafficStats.clearThreadStatsUid();
773 }
774 });
775 }
776
777 /** @hide */
778 @VisibleForTesting
779 public IpSecService(
780 Context context, IpSecServiceConfiguration config, UidFdTagger uidFdTagger) {
ludi1a06aa72017-05-12 09:15:00 -0700781 mContext = context;
782 mSrvConfig = config;
Benedict Wongbabe5d72017-12-03 19:42:36 -0800783 mUidFdTagger = uidFdTagger;
ludi1a06aa72017-05-12 09:15:00 -0700784 }
785
Nathan Harold1afbef42017-03-01 18:55:06 -0800786 public void systemReady() {
787 if (isNetdAlive()) {
788 Slog.d(TAG, "IpSecService is ready");
789 } else {
790 Slog.wtf(TAG, "IpSecService not ready: failed to connect to NetD Native Service!");
791 }
792 }
793
794 private void connectNativeNetdService() {
795 // Avoid blocking the system server to do this
Nathan Haroldb0e05082017-07-17 14:01:53 -0700796 new Thread() {
797 @Override
798 public void run() {
799 synchronized (IpSecService.this) {
ludi1a06aa72017-05-12 09:15:00 -0700800 NetdService.get(NETD_FETCH_TIMEOUT_MS);
Nathan Haroldb0e05082017-07-17 14:01:53 -0700801 }
802 }
803 }.start();
Nathan Harold1afbef42017-03-01 18:55:06 -0800804 }
805
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700806 synchronized boolean isNetdAlive() {
807 try {
ludi1a06aa72017-05-12 09:15:00 -0700808 final INetd netd = mSrvConfig.getNetdInstance();
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700809 if (netd == null) {
Nathan Harold1afbef42017-03-01 18:55:06 -0800810 return false;
811 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700812 return netd.isAlive();
813 } catch (RemoteException re) {
814 return false;
Nathan Harold1afbef42017-03-01 18:55:06 -0800815 }
816 }
817
Nathan Harolda10003d2017-08-23 13:46:33 -0700818 /**
819 * Checks that the provided InetAddress is valid for use in an IPsec SA. The address must not be
820 * a wildcard address and must be in a numeric form such as 1.2.3.4 or 2001::1.
821 */
822 private static void checkInetAddress(String inetAddress) {
823 if (TextUtils.isEmpty(inetAddress)) {
824 throw new IllegalArgumentException("Unspecified address");
825 }
826
827 InetAddress checkAddr = NetworkUtils.numericToInetAddress(inetAddress);
828
829 if (checkAddr.isAnyLocalAddress()) {
830 throw new IllegalArgumentException("Inappropriate wildcard address: " + inetAddress);
831 }
832 }
833
834 /**
835 * Checks the user-provided direction field and throws an IllegalArgumentException if it is not
836 * DIRECTION_IN or DIRECTION_OUT
837 */
838 private static void checkDirection(int direction) {
839 switch (direction) {
840 case IpSecTransform.DIRECTION_OUT:
841 case IpSecTransform.DIRECTION_IN:
842 return;
843 }
844 throw new IllegalArgumentException("Invalid Direction: " + direction);
845 }
846
Nathan Harold93962f32017-03-07 13:23:36 -0800847 /** Get a new SPI and maintain the reservation in the system server */
Jonathan Basseri5fb92902017-11-16 10:58:01 -0800848 @Override
849 public synchronized IpSecSpiResponse allocateSecurityParameterIndex(
Nathan Harold93962f32017-03-07 13:23:36 -0800850 int direction, String remoteAddress, int requestedSpi, IBinder binder)
851 throws RemoteException {
Nathan Harolda10003d2017-08-23 13:46:33 -0700852 checkDirection(direction);
853 checkInetAddress(remoteAddress);
854 /* requestedSpi can be anything in the int range, so no check is needed. */
Jonathan Basseri5fb92902017-11-16 10:58:01 -0800855 checkNotNull(binder, "Null Binder passed to allocateSecurityParameterIndex");
Nathan Harolda10003d2017-08-23 13:46:33 -0700856
Benedict Wong344bd622017-11-16 15:27:22 -0800857 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
Nathan Harold93962f32017-03-07 13:23:36 -0800858 int resourceId = mNextResourceId.getAndIncrement();
859
860 int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
861 String localAddress = "";
Nathan Harolda1afbd82017-04-24 16:16:34 -0700862
Nathan Harold93962f32017-03-07 13:23:36 -0800863 try {
Benedict Wong344bd622017-11-16 15:27:22 -0800864 if (!userRecord.mSpiQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700865 return new IpSecSpiResponse(
Nathan Harolda10003d2017-08-23 13:46:33 -0700866 IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
Nathan Harolda1afbd82017-04-24 16:16:34 -0700867 }
Nathan Harold93962f32017-03-07 13:23:36 -0800868 spi =
ludi1a06aa72017-05-12 09:15:00 -0700869 mSrvConfig
870 .getNetdInstance()
Nathan Harold93962f32017-03-07 13:23:36 -0800871 .ipSecAllocateSpi(
872 resourceId,
873 direction,
874 localAddress,
875 remoteAddress,
876 requestedSpi);
877 Log.d(TAG, "Allocated SPI " + spi);
Benedict Wong344bd622017-11-16 15:27:22 -0800878 userRecord.mSpiRecords.put(
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700879 resourceId,
Benedict Wong344bd622017-11-16 15:27:22 -0800880 new RefcountedResource<SpiRecord>(
881 new SpiRecord(resourceId, direction, localAddress, remoteAddress, spi),
882 binder));
Nathan Harold93962f32017-03-07 13:23:36 -0800883 } catch (ServiceSpecificException e) {
884 // TODO: Add appropriate checks when other ServiceSpecificException types are supported
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700885 return new IpSecSpiResponse(
Nathan Harolda1afbd82017-04-24 16:16:34 -0700886 IpSecManager.Status.SPI_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
Nathan Harold93962f32017-03-07 13:23:36 -0800887 } catch (RemoteException e) {
888 throw e.rethrowFromSystemServer();
889 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700890 return new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, spi);
891 }
892
893 /* This method should only be called from Binder threads. Do not call this from
894 * within the system server as it will crash the system on failure.
895 */
Benedict Wong344bd622017-11-16 15:27:22 -0800896 private void releaseResource(RefcountedResourceArray resArray, int resourceId)
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700897 throws RemoteException {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700898
Benedict Wong344bd622017-11-16 15:27:22 -0800899 resArray.getRefcountedResourceOrThrow(resourceId).userRelease();
Nathan Harold93962f32017-03-07 13:23:36 -0800900 }
901
902 /** Release a previously allocated SPI that has been registered with the system server */
903 @Override
Benedict Wong344bd622017-11-16 15:27:22 -0800904 public synchronized void releaseSecurityParameterIndex(int resourceId) throws RemoteException {
905 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
906 releaseResource(userRecord.mSpiRecords, resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700907 }
908
909 /**
910 * This function finds and forcibly binds to a random system port, ensuring that the port cannot
911 * be unbound.
912 *
913 * <p>A socket cannot be un-bound from a port if it was bound to that port by number. To select
914 * a random open port and then bind by number, this function creates a temp socket, binds to a
915 * random port (specifying 0), gets that port number, and then uses is to bind the user's UDP
916 * Encapsulation Socket forcibly, so that it cannot be un-bound by the user with the returned
917 * FileHandle.
918 *
919 * <p>The loop in this function handles the inherent race window between un-binding to a port
920 * and re-binding, during which the system could *technically* hand that port out to someone
921 * else.
922 */
Benedict Wongf186d672017-10-10 20:44:28 -0700923 private int bindToRandomPort(FileDescriptor sockFd) throws IOException {
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700924 for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
925 try {
926 FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
927 Os.bind(probeSocket, INADDR_ANY, 0);
928 int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
929 Os.close(probeSocket);
930 Log.v(TAG, "Binding to port " + port);
931 Os.bind(sockFd, INADDR_ANY, port);
Benedict Wongf186d672017-10-10 20:44:28 -0700932 return port;
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700933 } catch (ErrnoException e) {
934 // Someone miraculously claimed the port just after we closed probeSocket.
935 if (e.errno == OsConstants.EADDRINUSE) {
936 continue;
937 }
938 throw e.rethrowAsIOException();
939 }
940 }
941 throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
942 }
Nathan Harold93962f32017-03-07 13:23:36 -0800943
944 /**
Benedict Wongbabe5d72017-12-03 19:42:36 -0800945 * Functional interface to do traffic tagging of given sockets to UIDs.
946 *
947 * <p>Specifically used by openUdpEncapsulationSocket to ensure data usage on the UDP encap
948 * sockets are billed to the UID that the UDP encap socket was created on behalf of.
949 *
950 * <p>Separate class so that the socket tagging logic can be mocked; TrafficStats uses static
951 * methods that cannot be easily mocked/tested.
952 */
953 @VisibleForTesting
954 public interface UidFdTagger {
955 /**
956 * Sets socket tag to assign all traffic to the provided UID.
957 *
958 * <p>Since the socket is created on behalf of an unprivileged application, all traffic
959 * should be accounted to the UID of the unprivileged application.
960 */
961 public void tag(FileDescriptor fd, int uid) throws IOException;
962 }
963
964 /**
Nathan Harold93962f32017-03-07 13:23:36 -0800965 * Open a socket via the system server and bind it to the specified port (random if port=0).
966 * This will return a PFD to the user that represent a bound UDP socket. The system server will
967 * cache the socket and a record of its owner so that it can and must be freed when no longer
968 * needed.
969 */
970 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700971 public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
972 throws RemoteException {
973 if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) {
974 throw new IllegalArgumentException(
975 "Specified port number must be a valid non-reserved UDP port");
976 }
Nathan Harolda10003d2017-08-23 13:46:33 -0700977 checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket");
978
Benedict Wongbabe5d72017-12-03 19:42:36 -0800979 int callingUid = Binder.getCallingUid();
980 UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700981 int resourceId = mNextResourceId.getAndIncrement();
982 FileDescriptor sockFd = null;
983 try {
Benedict Wong344bd622017-11-16 15:27:22 -0800984 if (!userRecord.mSocketQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -0700985 return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
986 }
987
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700988 sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
Benedict Wongbabe5d72017-12-03 19:42:36 -0800989 mUidFdTagger.tag(sockFd, callingUid);
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700990
Nathan Harold8dc1fd02017-04-04 19:37:48 -0700991 // This code is common to both the unspecified and specified port cases
992 Os.setsockoptInt(
993 sockFd,
994 OsConstants.IPPROTO_UDP,
995 OsConstants.UDP_ENCAP,
996 OsConstants.UDP_ENCAP_ESPINUDP);
997
Benedict Wongba8d3132017-12-06 21:56:35 -0800998 mSrvConfig.getNetdInstance().ipSecSetEncapSocketOwner(sockFd, callingUid);
999 if (port != 0) {
1000 Log.v(TAG, "Binding to port " + port);
1001 Os.bind(sockFd, INADDR_ANY, port);
1002 } else {
1003 port = bindToRandomPort(sockFd);
1004 }
1005
Benedict Wong344bd622017-11-16 15:27:22 -08001006 userRecord.mEncapSocketRecords.put(
1007 resourceId,
1008 new RefcountedResource<EncapSocketRecord>(
1009 new EncapSocketRecord(resourceId, sockFd, port), binder));
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001010 return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd);
1011 } catch (IOException | ErrnoException e) {
1012 IoUtils.closeQuietly(sockFd);
1013 }
1014 // If we make it to here, then something has gone wrong and we couldn't open a socket.
1015 // The only reasonable condition that would cause that is resource unavailable.
1016 return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
Nathan Harold93962f32017-03-07 13:23:36 -08001017 }
1018
1019 /** close a socket that has been been allocated by and registered with the system server */
1020 @Override
Benedict Wong344bd622017-11-16 15:27:22 -08001021 public synchronized void closeUdpEncapsulationSocket(int resourceId) throws RemoteException {
1022 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1023 releaseResource(userRecord.mEncapSocketRecords, resourceId);
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001024 }
Nathan Harold93962f32017-03-07 13:23:36 -08001025
1026 /**
Nathan Harolda10003d2017-08-23 13:46:33 -07001027 * Checks an IpSecConfig parcel to ensure that the contents are sane and throws an
1028 * IllegalArgumentException if they are not.
1029 */
1030 private void checkIpSecConfig(IpSecConfig config) {
Benedict Wong344bd622017-11-16 15:27:22 -08001031 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1032
Nathan Harolda10003d2017-08-23 13:46:33 -07001033 if (config.getLocalAddress() == null) {
1034 throw new IllegalArgumentException("Invalid null Local InetAddress");
1035 }
1036
1037 if (config.getRemoteAddress() == null) {
1038 throw new IllegalArgumentException("Invalid null Remote InetAddress");
1039 }
1040
1041 switch (config.getMode()) {
1042 case IpSecTransform.MODE_TRANSPORT:
1043 if (!config.getLocalAddress().isEmpty()) {
1044 throw new IllegalArgumentException("Non-empty Local Address");
1045 }
1046 // Must be valid, and not a wildcard
1047 checkInetAddress(config.getRemoteAddress());
1048 break;
1049 case IpSecTransform.MODE_TUNNEL:
1050 break;
1051 default:
1052 throw new IllegalArgumentException(
1053 "Invalid IpSecTransform.mode: " + config.getMode());
1054 }
1055
1056 switch (config.getEncapType()) {
1057 case IpSecTransform.ENCAP_NONE:
1058 break;
1059 case IpSecTransform.ENCAP_ESPINUDP:
1060 case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
Benedict Wong344bd622017-11-16 15:27:22 -08001061 // Retrieve encap socket record; will throw IllegalArgumentException if not found
1062 userRecord.mEncapSocketRecords.getResourceOrThrow(
1063 config.getEncapSocketResourceId());
Nathan Harolda10003d2017-08-23 13:46:33 -07001064
1065 int port = config.getEncapRemotePort();
1066 if (port <= 0 || port > 0xFFFF) {
1067 throw new IllegalArgumentException("Invalid remote UDP port: " + port);
1068 }
1069 break;
1070 default:
1071 throw new IllegalArgumentException("Invalid Encap Type: " + config.getEncapType());
1072 }
1073
1074 for (int direction : DIRECTIONS) {
1075 IpSecAlgorithm crypt = config.getEncryption(direction);
1076 IpSecAlgorithm auth = config.getAuthentication(direction);
Benedict Wong0febe5e2017-08-22 21:42:33 -07001077 IpSecAlgorithm authenticatedEncryption = config.getAuthenticatedEncryption(direction);
1078 if (authenticatedEncryption == null && crypt == null && auth == null) {
1079 throw new IllegalArgumentException(
1080 "No Encryption or Authentication algorithms specified");
1081 } else if (authenticatedEncryption != null && (auth != null || crypt != null)) {
1082 throw new IllegalArgumentException(
1083 "Authenticated Encryption is mutually"
1084 + " exclusive with other Authentication or Encryption algorithms");
Nathan Harolda10003d2017-08-23 13:46:33 -07001085 }
1086
Benedict Wong344bd622017-11-16 15:27:22 -08001087 // Retrieve SPI record; will throw IllegalArgumentException if not found
1088 userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId(direction));
Nathan Harolda10003d2017-08-23 13:46:33 -07001089 }
1090 }
1091
1092 /**
Nathan Harold93962f32017-03-07 13:23:36 -08001093 * Create a transport mode transform, which represent two security associations (one in each
1094 * direction) in the kernel. The transform will be cached by the system server and must be freed
1095 * when no longer needed. It is possible to free one, deleting the SA from underneath sockets
1096 * that are using it, which will result in all of those sockets becoming unable to send or
1097 * receive data.
1098 */
1099 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001100 public synchronized IpSecTransformResponse createTransportModeTransform(
1101 IpSecConfig c, IBinder binder) throws RemoteException {
Nathan Harolda10003d2017-08-23 13:46:33 -07001102 checkIpSecConfig(c);
1103 checkNotNull(binder, "Null Binder passed to createTransportModeTransform");
Nathan Harold93962f32017-03-07 13:23:36 -08001104 int resourceId = mNextResourceId.getAndIncrement();
Benedict Wong344bd622017-11-16 15:27:22 -08001105
1106 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1107
1108 // Avoid resizing by creating a dependency array of min-size 3 (1 UDP encap + 2 SPIs)
1109 List<RefcountedResource> dependencies = new ArrayList<>(3);
1110
1111 if (!userRecord.mTransformQuotaTracker.isAvailable()) {
Nathan Harolda1afbd82017-04-24 16:16:34 -07001112 return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
1113 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001114 SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
Nathan Harolda10003d2017-08-23 13:46:33 -07001115
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001116 int encapType, encapLocalPort = 0, encapRemotePort = 0;
Benedict Wong344bd622017-11-16 15:27:22 -08001117 EncapSocketRecord socketRecord = null;
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001118 encapType = c.getEncapType();
1119 if (encapType != IpSecTransform.ENCAP_NONE) {
Benedict Wong344bd622017-11-16 15:27:22 -08001120 RefcountedResource<EncapSocketRecord> refcountedSocketRecord =
1121 userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(
1122 c.getEncapSocketResourceId());
1123 dependencies.add(refcountedSocketRecord);
1124
1125 socketRecord = refcountedSocketRecord.getResource();
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001126 encapLocalPort = socketRecord.getPort();
1127 encapRemotePort = c.getEncapRemotePort();
1128 }
1129
Nathan Harold93962f32017-03-07 13:23:36 -08001130 for (int direction : DIRECTIONS) {
1131 IpSecAlgorithm auth = c.getAuthentication(direction);
1132 IpSecAlgorithm crypt = c.getEncryption(direction);
Benedict Wong0febe5e2017-08-22 21:42:33 -07001133 IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(direction);
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001134
Benedict Wong344bd622017-11-16 15:27:22 -08001135 RefcountedResource<SpiRecord> refcountedSpiRecord =
1136 userRecord.mSpiRecords.getRefcountedResourceOrThrow(
1137 c.getSpiResourceId(direction));
1138 dependencies.add(refcountedSpiRecord);
1139
1140 spis[direction] = refcountedSpiRecord.getResource();
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001141 int spi = spis[direction].getSpi();
Nathan Harold93962f32017-03-07 13:23:36 -08001142 try {
Nathan Harolda10003d2017-08-23 13:46:33 -07001143 mSrvConfig
1144 .getNetdInstance()
ludi0f807892017-05-20 14:15:09 -07001145 .ipSecAddSecurityAssociation(
1146 resourceId,
1147 c.getMode(),
1148 direction,
Nathan Harolda10003d2017-08-23 13:46:33 -07001149 c.getLocalAddress(),
1150 c.getRemoteAddress(),
1151 (c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0,
ludi0f807892017-05-20 14:15:09 -07001152 spi,
1153 (auth != null) ? auth.getName() : "",
Manoj Boopathi Rajfffa8112017-10-26 11:49:02 -07001154 (auth != null) ? auth.getKey() : new byte[] {},
ludi0f807892017-05-20 14:15:09 -07001155 (auth != null) ? auth.getTruncationLengthBits() : 0,
1156 (crypt != null) ? crypt.getName() : "",
Manoj Boopathi Rajfffa8112017-10-26 11:49:02 -07001157 (crypt != null) ? crypt.getKey() : new byte[] {},
ludi0f807892017-05-20 14:15:09 -07001158 (crypt != null) ? crypt.getTruncationLengthBits() : 0,
Benedict Wong0febe5e2017-08-22 21:42:33 -07001159 (authCrypt != null) ? authCrypt.getName() : "",
Manoj Boopathi Rajfffa8112017-10-26 11:49:02 -07001160 (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
Benedict Wong0febe5e2017-08-22 21:42:33 -07001161 (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
ludi0f807892017-05-20 14:15:09 -07001162 encapType,
1163 encapLocalPort,
1164 encapRemotePort);
Nathan Harold93962f32017-03-07 13:23:36 -08001165 } catch (ServiceSpecificException e) {
1166 // FIXME: get the error code and throw is at an IOException from Errno Exception
ludi0f807892017-05-20 14:15:09 -07001167 return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
Nathan Harold93962f32017-03-07 13:23:36 -08001168 }
1169 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001170 // Both SAs were created successfully, time to construct a record and lock it away
Benedict Wong344bd622017-11-16 15:27:22 -08001171 userRecord.mTransformRecords.put(
1172 resourceId,
1173 new RefcountedResource<TransformRecord>(
1174 new TransformRecord(resourceId, c, spis, socketRecord),
1175 binder,
1176 dependencies.toArray(new RefcountedResource[dependencies.size()])));
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001177 return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001178 }
1179
1180 /**
1181 * Delete a transport mode transform that was previously allocated by + registered with the
1182 * system server. If this is called on an inactive (or non-existent) transform, it will not
1183 * return an error. It's safe to de-allocate transforms that may have already been deleted for
1184 * other reasons.
1185 */
1186 @Override
Benedict Wong344bd622017-11-16 15:27:22 -08001187 public synchronized void deleteTransportModeTransform(int resourceId) throws RemoteException {
1188 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
1189 releaseResource(userRecord.mTransformRecords, resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001190 }
1191
1192 /**
1193 * Apply an active transport mode transform to a socket, which will apply the IPsec security
1194 * association as a correspondent policy to the provided socket
1195 */
1196 @Override
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001197 public synchronized void applyTransportModeTransform(
1198 ParcelFileDescriptor socket, int resourceId) throws RemoteException {
Benedict Wong344bd622017-11-16 15:27:22 -08001199 UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
Nathan Harold93962f32017-03-07 13:23:36 -08001200
Benedict Wong344bd622017-11-16 15:27:22 -08001201 // Get transform record; if no transform is found, will throw IllegalArgumentException
1202 TransformRecord info = userRecord.mTransformRecords.getResourceOrThrow(resourceId);
Nathan Harold93962f32017-03-07 13:23:36 -08001203
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001204 // TODO: make this a function.
1205 if (info.pid != getCallingPid() || info.uid != getCallingUid()) {
1206 throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
1207 }
1208
1209 IpSecConfig c = info.getConfig();
1210 try {
1211 for (int direction : DIRECTIONS) {
ludi1a06aa72017-05-12 09:15:00 -07001212 mSrvConfig
1213 .getNetdInstance()
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001214 .ipSecApplyTransportModeTransform(
1215 socket.getFileDescriptor(),
1216 resourceId,
1217 direction,
Nathan Harolda10003d2017-08-23 13:46:33 -07001218 c.getLocalAddress(),
1219 c.getRemoteAddress(),
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001220 info.getSpiRecord(direction).getSpi());
Nathan Harold93962f32017-03-07 13:23:36 -08001221 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001222 } catch (ServiceSpecificException e) {
1223 // FIXME: get the error code and throw is at an IOException from Errno Exception
Nathan Harold93962f32017-03-07 13:23:36 -08001224 }
1225 }
Nathan Harold8dc1fd02017-04-04 19:37:48 -07001226
Nathan Harold93962f32017-03-07 13:23:36 -08001227 /**
1228 * Remove a transport mode transform from a socket, applying the default (empty) policy. This
1229 * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of
1230 * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not
1231 * used: reserved for future improved input validation.
1232 */
1233 @Override
Benedict Wong344bd622017-11-16 15:27:22 -08001234 public synchronized void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId)
Nathan Harold93962f32017-03-07 13:23:36 -08001235 throws RemoteException {
1236 try {
ludi1a06aa72017-05-12 09:15:00 -07001237 mSrvConfig
1238 .getNetdInstance()
1239 .ipSecRemoveTransportModeTransform(socket.getFileDescriptor());
Nathan Harold93962f32017-03-07 13:23:36 -08001240 } catch (ServiceSpecificException e) {
1241 // FIXME: get the error code and throw is at an IOException from Errno Exception
1242 }
1243 }
1244
1245 @Override
ludib0c95b12017-05-22 10:52:23 -07001246 protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Nathan Harold1afbef42017-03-01 18:55:06 -08001247 mContext.enforceCallingOrSelfPermission(DUMP, TAG);
ludib0c95b12017-05-22 10:52:23 -07001248
1249 pw.println("IpSecService dump:");
Nathan Harold1afbef42017-03-01 18:55:06 -08001250 pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead"));
1251 pw.println();
ludib0c95b12017-05-22 10:52:23 -07001252
Benedict Wong344bd622017-11-16 15:27:22 -08001253 pw.println("mUserResourceTracker:");
1254 pw.println(mUserResourceTracker);
Nathan Harold1afbef42017-03-01 18:55:06 -08001255 }
1256}