1 /* 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 17 package com.android.server; 18 19 import static android.Manifest.permission.DUMP; 20 import static android.net.IpSecManager.INVALID_RESOURCE_ID; 21 import static android.system.OsConstants.AF_INET; 22 import static android.system.OsConstants.IPPROTO_UDP; 23 import static android.system.OsConstants.SOCK_DGRAM; 24 import static com.android.internal.util.Preconditions.checkNotNull; 25 26 import android.content.Context; 27 import android.net.IIpSecService; 28 import android.net.INetd; 29 import android.net.IpSecAlgorithm; 30 import android.net.IpSecConfig; 31 import android.net.IpSecManager; 32 import android.net.IpSecSpiResponse; 33 import android.net.IpSecTransform; 34 import android.net.IpSecTransformResponse; 35 import android.net.IpSecUdpEncapResponse; 36 import android.net.util.NetdService; 37 import android.os.Binder; 38 import android.os.IBinder; 39 import android.os.ParcelFileDescriptor; 40 import android.os.RemoteException; 41 import android.os.ServiceSpecificException; 42 import android.system.ErrnoException; 43 import android.system.Os; 44 import android.system.OsConstants; 45 import android.util.Log; 46 import android.util.Slog; 47 import android.util.SparseArray; 48 import com.android.internal.annotations.GuardedBy; 49 import java.io.FileDescriptor; 50 import java.io.IOException; 51 import java.io.PrintWriter; 52 import java.net.InetAddress; 53 import java.net.InetSocketAddress; 54 import java.net.UnknownHostException; 55 import java.util.concurrent.atomic.AtomicInteger; 56 import libcore.io.IoUtils; 57 58 /** @hide */ 59 public class IpSecService extends IIpSecService.Stub { 60 private static final String TAG = "IpSecService"; 61 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 62 63 private static final String NETD_SERVICE_NAME = "netd"; 64 private static final int[] DIRECTIONS = 65 new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN}; 66 67 private static final int NETD_FETCH_TIMEOUT = 5000; //ms 68 private static final int MAX_PORT_BIND_ATTEMPTS = 10; 69 private static final InetAddress INADDR_ANY; 70 71 static { 72 try { 73 INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0}); 74 } catch (UnknownHostException e) { 75 throw new RuntimeException(e); 76 } 77 } 78 79 static final int FREE_PORT_MIN = 1024; // ports 1-1023 are reserved 80 static final int PORT_MAX = 0xFFFF; // ports are an unsigned 16-bit integer 81 82 /* Binder context for this service */ 83 private final Context mContext; 84 85 /** Should be a never-repeating global ID for resources */ 86 private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0); 87 88 @GuardedBy("this") 89 private final ManagedResourceArray<SpiRecord> mSpiRecords = new ManagedResourceArray<>(); 90 91 @GuardedBy("this") 92 private final ManagedResourceArray<TransformRecord> mTransformRecords = 93 new ManagedResourceArray<>(); 94 95 @GuardedBy("this") 96 private final ManagedResourceArray<UdpSocketRecord> mUdpSocketRecords = 97 new ManagedResourceArray<>(); 98 99 /** 100 * The ManagedResource class provides a facility to cleanly and reliably release system 101 * resources. It relies on two things: an IBinder that allows ManagedResource to automatically 102 * clean up in the event that the Binder dies and a user-provided resourceId that should 103 * uniquely identify the managed resource. To use this class, the user should implement the 104 * releaseResources() method that is responsible for releasing system resources when invoked. 105 */ 106 private abstract class ManagedResource implements IBinder.DeathRecipient { 107 final int pid; 108 final int uid; 109 private IBinder mBinder; 110 protected int mResourceId; 111 112 private AtomicInteger mReferenceCount = new AtomicInteger(0); 113 114 ManagedResource(int resourceId, IBinder binder) { 115 super(); 116 mBinder = binder; 117 mResourceId = resourceId; 118 pid = Binder.getCallingPid(); 119 uid = Binder.getCallingUid(); 120 121 try { 122 mBinder.linkToDeath(this, 0); 123 } catch (RemoteException e) { 124 binderDied(); 125 } 126 } 127 128 public void addReference() { 129 mReferenceCount.incrementAndGet(); 130 } 131 132 public void removeReference() { 133 if (mReferenceCount.decrementAndGet() < 0) { 134 Log.wtf(TAG, "Programming error: negative reference count"); 135 } 136 } 137 138 public boolean isReferenced() { 139 return (mReferenceCount.get() > 0); 140 } 141 142 public void checkOwnerOrSystemAndThrow() { 143 if (uid != Binder.getCallingUid() 144 && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) { 145 throw new SecurityException("Only the owner may access managed resources!"); 146 } 147 } 148 149 /** 150 * When this record is no longer needed for managing system resources this function should 151 * clean up all system resources and nullify the record. This function shall perform all 152 * necessary cleanup of the resources managed by this record. 153 * 154 * <p>NOTE: this function verifies ownership before allowing resources to be freed. 155 */ 156 public final void release() throws RemoteException { 157 synchronized (IpSecService.this) { 158 if (isReferenced()) { 159 throw new IllegalStateException( 160 "Cannot release a resource that has active references!"); 161 } 162 163 if (mResourceId == INVALID_RESOURCE_ID) { 164 return; 165 } 166 167 releaseResources(); 168 if (mBinder != null) { 169 mBinder.unlinkToDeath(this, 0); 170 } 171 mBinder = null; 172 173 mResourceId = INVALID_RESOURCE_ID; 174 } 175 } 176 177 /** 178 * If the Binder object dies, this function is called to free the system resources that are 179 * being managed by this record and to subsequently release this record for garbage 180 * collection 181 */ 182 public final void binderDied() { 183 try { 184 release(); 185 } catch (Exception e) { 186 Log.e(TAG, "Failed to release resource: " + e); 187 } 188 } 189 190 /** 191 * Implement this method to release all system resources that are being protected by this 192 * record. Once the resources are released, the record should be invalidated and no longer 193 * used by calling release(). This should NEVER be called directly. 194 * 195 * <p>Calls to this are always guarded by IpSecService#this 196 */ 197 protected abstract void releaseResources() throws RemoteException; 198 }; 199 200 /** 201 * Minimal wrapper around SparseArray that performs ownership 202 * validation on element accesses. 203 */ 204 private class ManagedResourceArray<T extends ManagedResource> { 205 SparseArray<T> mArray = new SparseArray<>(); 206 207 T get(int key) { 208 T val = mArray.get(key); 209 // The value should never be null unless the resource doesn't exist 210 // (since we do not allow null resources to be added). 211 if (val != null) { 212 val.checkOwnerOrSystemAndThrow(); 213 } 214 return val; 215 } 216 217 void put(int key, T obj) { 218 checkNotNull(obj, "Null resources cannot be added"); 219 mArray.put(key, obj); 220 } 221 222 void remove(int key) { 223 mArray.remove(key); 224 } 225 } 226 227 private final class TransformRecord extends ManagedResource { 228 private final IpSecConfig mConfig; 229 private final SpiRecord[] mSpis; 230 private final UdpSocketRecord mSocket; 231 232 TransformRecord( 233 int resourceId, 234 IBinder binder, 235 IpSecConfig config, 236 SpiRecord[] spis, 237 UdpSocketRecord socket) { 238 super(resourceId, binder); 239 mConfig = config; 240 mSpis = spis; 241 mSocket = socket; 242 243 for (int direction : DIRECTIONS) { 244 mSpis[direction].addReference(); 245 mSpis[direction].setOwnedByTransform(); 246 } 247 248 if (mSocket != null) { 249 mSocket.addReference(); 250 } 251 } 252 253 public IpSecConfig getConfig() { 254 return mConfig; 255 } 256 257 public SpiRecord getSpiRecord(int direction) { 258 return mSpis[direction]; 259 } 260 261 /** always guarded by IpSecService#this */ 262 @Override 263 protected void releaseResources() { 264 for (int direction : DIRECTIONS) { 265 int spi = mSpis[direction].getSpi(); 266 try { 267 getNetdInstance() 268 .ipSecDeleteSecurityAssociation( 269 mResourceId, 270 direction, 271 (mConfig.getLocalAddress() != null) 272 ? mConfig.getLocalAddress().getHostAddress() 273 : "", 274 (mConfig.getRemoteAddress() != null) 275 ? mConfig.getRemoteAddress().getHostAddress() 276 : "", 277 spi); 278 } catch (ServiceSpecificException e) { 279 // FIXME: get the error code and throw is at an IOException from Errno Exception 280 } catch (RemoteException e) { 281 Log.e(TAG, "Failed to delete SA with ID: " + mResourceId); 282 } 283 } 284 285 for (int direction : DIRECTIONS) { 286 mSpis[direction].removeReference(); 287 } 288 289 if (mSocket != null) { 290 mSocket.removeReference(); 291 } 292 } 293 } 294 295 private final class SpiRecord extends ManagedResource { 296 private final int mDirection; 297 private final String mLocalAddress; 298 private final String mRemoteAddress; 299 private int mSpi; 300 301 private boolean mOwnedByTransform = false; 302 303 SpiRecord( 304 int resourceId, 305 IBinder binder, 306 int direction, 307 String localAddress, 308 String remoteAddress, 309 int spi) { 310 super(resourceId, binder); 311 mDirection = direction; 312 mLocalAddress = localAddress; 313 mRemoteAddress = remoteAddress; 314 mSpi = spi; 315 } 316 317 /** always guarded by IpSecService#this */ 318 @Override 319 protected void releaseResources() { 320 if (mOwnedByTransform) { 321 Log.d(TAG, "Cannot release Spi " + mSpi + ": Currently locked by a Transform"); 322 // Because SPIs are "handed off" to transform, objects, they should never be 323 // freed from the SpiRecord once used in a transform. (They refer to the same SA, 324 // thus ownership and responsibility for freeing these resources passes to the 325 // Transform object). Thus, we should let the user free them without penalty once 326 // they are applied in a Transform object. 327 return; 328 } 329 330 try { 331 getNetdInstance() 332 .ipSecDeleteSecurityAssociation( 333 mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi); 334 } catch (ServiceSpecificException e) { 335 // FIXME: get the error code and throw is at an IOException from Errno Exception 336 } catch (RemoteException e) { 337 Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId); 338 } 339 340 mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX; 341 } 342 343 public int getSpi() { 344 return mSpi; 345 } 346 347 public void setOwnedByTransform() { 348 if (mOwnedByTransform) { 349 // Programming error 350 throw new IllegalStateException("Cannot own an SPI twice!"); 351 } 352 353 mOwnedByTransform = true; 354 } 355 } 356 357 private final class UdpSocketRecord extends ManagedResource { 358 private FileDescriptor mSocket; 359 private final int mPort; 360 361 UdpSocketRecord(int resourceId, IBinder binder, FileDescriptor socket, int port) { 362 super(resourceId, binder); 363 mSocket = socket; 364 mPort = port; 365 } 366 367 /** always guarded by IpSecService#this */ 368 @Override 369 protected void releaseResources() { 370 Log.d(TAG, "Closing port " + mPort); 371 IoUtils.closeQuietly(mSocket); 372 mSocket = null; 373 } 374 375 public int getPort() { 376 return mPort; 377 } 378 379 public FileDescriptor getSocket() { 380 return mSocket; 381 } 382 } 383 384 /** 385 * Constructs a new IpSecService instance 386 * 387 * @param context Binder context for this service 388 */ 389 private IpSecService(Context context) { 390 mContext = context; 391 } 392 393 static IpSecService create(Context context) throws InterruptedException { 394 final IpSecService service = new IpSecService(context); 395 service.connectNativeNetdService(); 396 return service; 397 } 398 399 public void systemReady() { 400 if (isNetdAlive()) { 401 Slog.d(TAG, "IpSecService is ready"); 402 } else { 403 Slog.wtf(TAG, "IpSecService not ready: failed to connect to NetD Native Service!"); 404 } 405 } 406 407 private void connectNativeNetdService() { 408 // Avoid blocking the system server to do this 409 new Thread() { 410 @Override 411 public void run() { 412 synchronized (IpSecService.this) { 413 NetdService.get(NETD_FETCH_TIMEOUT); 414 } 415 } 416 }.start(); 417 } 418 419 INetd getNetdInstance() throws RemoteException { 420 final INetd netd = NetdService.getInstance(); 421 if (netd == null) { 422 throw new RemoteException("Failed to Get Netd Instance"); 423 } 424 return netd; 425 } 426 427 synchronized boolean isNetdAlive() { 428 try { 429 final INetd netd = getNetdInstance(); 430 if (netd == null) { 431 return false; 432 } 433 return netd.isAlive(); 434 } catch (RemoteException re) { 435 return false; 436 } 437 } 438 439 @Override 440 /** Get a new SPI and maintain the reservation in the system server */ 441 public synchronized IpSecSpiResponse reserveSecurityParameterIndex( 442 int direction, String remoteAddress, int requestedSpi, IBinder binder) 443 throws RemoteException { 444 int resourceId = mNextResourceId.getAndIncrement(); 445 446 int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX; 447 String localAddress = ""; 448 try { 449 spi = 450 getNetdInstance() 451 .ipSecAllocateSpi( 452 resourceId, 453 direction, 454 localAddress, 455 remoteAddress, 456 requestedSpi); 457 Log.d(TAG, "Allocated SPI " + spi); 458 mSpiRecords.put( 459 resourceId, 460 new SpiRecord(resourceId, binder, direction, localAddress, remoteAddress, spi)); 461 } catch (ServiceSpecificException e) { 462 // TODO: Add appropriate checks when other ServiceSpecificException types are supported 463 return new IpSecSpiResponse( 464 IpSecManager.Status.SPI_UNAVAILABLE, IpSecManager.INVALID_RESOURCE_ID, spi); 465 } catch (RemoteException e) { 466 throw e.rethrowFromSystemServer(); 467 } 468 return new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, spi); 469 } 470 471 /* This method should only be called from Binder threads. Do not call this from 472 * within the system server as it will crash the system on failure. 473 */ 474 private synchronized <T extends ManagedResource> void releaseManagedResource( 475 ManagedResourceArray<T> resArray, int resourceId, String typeName) 476 throws RemoteException { 477 // We want to non-destructively get so that we can check credentials before removing 478 // this from the records. 479 T record = resArray.get(resourceId); 480 481 if (record == null) { 482 throw new IllegalArgumentException( 483 typeName + " " + resourceId + " is not available to be deleted"); 484 } 485 486 record.release(); 487 resArray.remove(resourceId); 488 } 489 490 /** Release a previously allocated SPI that has been registered with the system server */ 491 @Override 492 public void releaseSecurityParameterIndex(int resourceId) throws RemoteException { 493 releaseManagedResource(mSpiRecords, resourceId, "SecurityParameterIndex"); 494 } 495 496 /** 497 * This function finds and forcibly binds to a random system port, ensuring that the port cannot 498 * be unbound. 499 * 500 * <p>A socket cannot be un-bound from a port if it was bound to that port by number. To select 501 * a random open port and then bind by number, this function creates a temp socket, binds to a 502 * random port (specifying 0), gets that port number, and then uses is to bind the user's UDP 503 * Encapsulation Socket forcibly, so that it cannot be un-bound by the user with the returned 504 * FileHandle. 505 * 506 * <p>The loop in this function handles the inherent race window between un-binding to a port 507 * and re-binding, during which the system could *technically* hand that port out to someone 508 * else. 509 */ 510 private void bindToRandomPort(FileDescriptor sockFd) throws IOException { 511 for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) { 512 try { 513 FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 514 Os.bind(probeSocket, INADDR_ANY, 0); 515 int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort(); 516 Os.close(probeSocket); 517 Log.v(TAG, "Binding to port " + port); 518 Os.bind(sockFd, INADDR_ANY, port); 519 return; 520 } catch (ErrnoException e) { 521 // Someone miraculously claimed the port just after we closed probeSocket. 522 if (e.errno == OsConstants.EADDRINUSE) { 523 continue; 524 } 525 throw e.rethrowAsIOException(); 526 } 527 } 528 throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port"); 529 } 530 531 /** 532 * Open a socket via the system server and bind it to the specified port (random if port=0). 533 * This will return a PFD to the user that represent a bound UDP socket. The system server will 534 * cache the socket and a record of its owner so that it can and must be freed when no longer 535 * needed. 536 */ 537 @Override 538 public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder) 539 throws RemoteException { 540 if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) { 541 throw new IllegalArgumentException( 542 "Specified port number must be a valid non-reserved UDP port"); 543 } 544 int resourceId = mNextResourceId.getAndIncrement(); 545 FileDescriptor sockFd = null; 546 try { 547 sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 548 549 if (port != 0) { 550 Log.v(TAG, "Binding to port " + port); 551 Os.bind(sockFd, INADDR_ANY, port); 552 } else { 553 bindToRandomPort(sockFd); 554 } 555 // This code is common to both the unspecified and specified port cases 556 Os.setsockoptInt( 557 sockFd, 558 OsConstants.IPPROTO_UDP, 559 OsConstants.UDP_ENCAP, 560 OsConstants.UDP_ENCAP_ESPINUDP); 561 562 mUdpSocketRecords.put( 563 resourceId, new UdpSocketRecord(resourceId, binder, sockFd, port)); 564 return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd); 565 } catch (IOException | ErrnoException e) { 566 IoUtils.closeQuietly(sockFd); 567 } 568 // If we make it to here, then something has gone wrong and we couldn't open a socket. 569 // The only reasonable condition that would cause that is resource unavailable. 570 return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); 571 } 572 573 /** close a socket that has been been allocated by and registered with the system server */ 574 @Override 575 public void closeUdpEncapsulationSocket(int resourceId) throws RemoteException { 576 577 releaseManagedResource(mUdpSocketRecords, resourceId, "UdpEncapsulationSocket"); 578 } 579 580 /** 581 * Create a transport mode transform, which represent two security associations (one in each 582 * direction) in the kernel. The transform will be cached by the system server and must be freed 583 * when no longer needed. It is possible to free one, deleting the SA from underneath sockets 584 * that are using it, which will result in all of those sockets becoming unable to send or 585 * receive data. 586 */ 587 @Override 588 public synchronized IpSecTransformResponse createTransportModeTransform( 589 IpSecConfig c, IBinder binder) throws RemoteException { 590 int resourceId = mNextResourceId.getAndIncrement(); 591 SpiRecord[] spis = new SpiRecord[DIRECTIONS.length]; 592 // TODO: Basic input validation here since it's coming over the Binder 593 int encapType, encapLocalPort = 0, encapRemotePort = 0; 594 UdpSocketRecord socketRecord = null; 595 encapType = c.getEncapType(); 596 if (encapType != IpSecTransform.ENCAP_NONE) { 597 socketRecord = mUdpSocketRecords.get(c.getEncapLocalResourceId()); 598 encapLocalPort = socketRecord.getPort(); 599 encapRemotePort = c.getEncapRemotePort(); 600 } 601 602 for (int direction : DIRECTIONS) { 603 IpSecAlgorithm auth = c.getAuthentication(direction); 604 IpSecAlgorithm crypt = c.getEncryption(direction); 605 606 spis[direction] = mSpiRecords.get(c.getSpiResourceId(direction)); 607 int spi = spis[direction].getSpi(); 608 try { 609 getNetdInstance() 610 .ipSecAddSecurityAssociation( 611 resourceId, 612 c.getMode(), 613 direction, 614 (c.getLocalAddress() != null) 615 ? c.getLocalAddress().getHostAddress() 616 : "", 617 (c.getRemoteAddress() != null) 618 ? c.getRemoteAddress().getHostAddress() 619 : "", 620 (c.getNetwork() != null) 621 ? c.getNetwork().getNetworkHandle() 622 : 0, 623 spi, 624 (auth != null) ? auth.getName() : "", 625 (auth != null) ? auth.getKey() : null, 626 (auth != null) ? auth.getTruncationLengthBits() : 0, 627 (crypt != null) ? crypt.getName() : "", 628 (crypt != null) ? crypt.getKey() : null, 629 (crypt != null) ? crypt.getTruncationLengthBits() : 0, 630 encapType, 631 encapLocalPort, 632 encapRemotePort); 633 } catch (ServiceSpecificException e) { 634 // FIXME: get the error code and throw is at an IOException from Errno Exception 635 return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); 636 } 637 } 638 // Both SAs were created successfully, time to construct a record and lock it away 639 mTransformRecords.put( 640 resourceId, new TransformRecord(resourceId, binder, c, spis, socketRecord)); 641 return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId); 642 } 643 644 /** 645 * Delete a transport mode transform that was previously allocated by + registered with the 646 * system server. If this is called on an inactive (or non-existent) transform, it will not 647 * return an error. It's safe to de-allocate transforms that may have already been deleted for 648 * other reasons. 649 */ 650 @Override 651 public void deleteTransportModeTransform(int resourceId) throws RemoteException { 652 releaseManagedResource(mTransformRecords, resourceId, "IpSecTransform"); 653 } 654 655 /** 656 * Apply an active transport mode transform to a socket, which will apply the IPsec security 657 * association as a correspondent policy to the provided socket 658 */ 659 @Override 660 public synchronized void applyTransportModeTransform( 661 ParcelFileDescriptor socket, int resourceId) throws RemoteException { 662 // Synchronize liberally here because we are using ManagedResources in this block 663 TransformRecord info; 664 // FIXME: this code should be factored out into a security check + getter 665 info = mTransformRecords.get(resourceId); 666 667 if (info == null) { 668 throw new IllegalArgumentException("Transform " + resourceId + " is not active"); 669 } 670 671 // TODO: make this a function. 672 if (info.pid != getCallingPid() || info.uid != getCallingUid()) { 673 throw new SecurityException("Only the owner of an IpSec Transform may apply it!"); 674 } 675 676 IpSecConfig c = info.getConfig(); 677 try { 678 for (int direction : DIRECTIONS) { 679 getNetdInstance() 680 .ipSecApplyTransportModeTransform( 681 socket.getFileDescriptor(), 682 resourceId, 683 direction, 684 (c.getLocalAddress() != null) 685 ? c.getLocalAddress().getHostAddress() 686 : "", 687 (c.getRemoteAddress() != null) 688 ? c.getRemoteAddress().getHostAddress() 689 : "", 690 info.getSpiRecord(direction).getSpi()); 691 } 692 } catch (ServiceSpecificException e) { 693 // FIXME: get the error code and throw is at an IOException from Errno Exception 694 } 695 } 696 697 /** 698 * Remove a transport mode transform from a socket, applying the default (empty) policy. This 699 * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of 700 * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not 701 * used: reserved for future improved input validation. 702 */ 703 @Override 704 public void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId) 705 throws RemoteException { 706 try { 707 getNetdInstance().ipSecRemoveTransportModeTransform(socket.getFileDescriptor()); 708 } catch (ServiceSpecificException e) { 709 // FIXME: get the error code and throw is at an IOException from Errno Exception 710 } 711 } 712 713 @Override 714 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 715 mContext.enforceCallingOrSelfPermission(DUMP, TAG); 716 // TODO: Add dump code to print out a log of all the resources being tracked 717 pw.println("IpSecService Log:"); 718 pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead")); 719 pw.println(); 720 } 721 } 722