diff options
| -rw-r--r-- | core/java/android/net/IIpSecService.aidl | 11 | ||||
| -rw-r--r-- | core/java/android/net/IpSecConfig.java | 24 | ||||
| -rw-r--r-- | core/java/android/net/IpSecManager.java | 145 | ||||
| -rw-r--r-- | core/java/android/net/IpSecSpiResponse.aidl | 20 | ||||
| -rw-r--r-- | core/java/android/net/IpSecSpiResponse.java | 78 | ||||
| -rw-r--r-- | core/java/android/net/IpSecTransform.java | 33 | ||||
| -rw-r--r-- | core/java/android/net/IpSecTransformResponse.aidl | 20 | ||||
| -rw-r--r-- | core/java/android/net/IpSecTransformResponse.java | 73 | ||||
| -rw-r--r-- | core/java/android/net/IpSecUdpEncapResponse.aidl | 20 | ||||
| -rw-r--r-- | core/java/android/net/IpSecUdpEncapResponse.java | 96 | ||||
| -rw-r--r-- | services/core/java/com/android/server/IpSecService.java | 547 |
11 files changed, 831 insertions, 236 deletions
diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl index 0aa3ce66eb29..0b1ea98f2e12 100644 --- a/core/java/android/net/IIpSecService.aidl +++ b/core/java/android/net/IIpSecService.aidl @@ -18,6 +18,9 @@ package android.net; import android.net.Network; import android.net.IpSecConfig; +import android.net.IpSecUdpEncapResponse; +import android.net.IpSecSpiResponse; +import android.net.IpSecTransformResponse; import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; @@ -27,16 +30,16 @@ import android.os.ParcelFileDescriptor; */ interface IIpSecService { - Bundle reserveSecurityParameterIndex( + IpSecSpiResponse reserveSecurityParameterIndex( int direction, in String remoteAddress, int requestedSpi, in IBinder binder); void releaseSecurityParameterIndex(int resourceId); - Bundle openUdpEncapsulationSocket(int port, in IBinder binder); + IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, in IBinder binder); - void closeUdpEncapsulationSocket(in ParcelFileDescriptor socket); + void closeUdpEncapsulationSocket(int resourceId); - Bundle createTransportModeTransform(in IpSecConfig c, in IBinder binder); + IpSecTransformResponse createTransportModeTransform(in IpSecConfig c, in IBinder binder); void deleteTransportModeTransform(int transformId); diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java index 13dc19f68577..8b80f2ba504d 100644 --- a/core/java/android/net/IpSecConfig.java +++ b/core/java/android/net/IpSecConfig.java @@ -40,7 +40,7 @@ public final class IpSecConfig implements Parcelable { // Minimum requirements for identifying a transform // SPI identifying the IPsec flow in packet processing // and a remote IP address - int spi; + int spiResourceId; // Encryption Algorithm IpSecAlgorithm encryption; @@ -54,7 +54,7 @@ public final class IpSecConfig implements Parcelable { // For tunnel mode IPv4 UDP Encapsulation // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE int encapType; - int encapLocalPort; + int encapLocalPortResourceId; int encapRemotePort; // An interval, in seconds between the NattKeepalive packets @@ -69,8 +69,8 @@ public final class IpSecConfig implements Parcelable { return localAddress; } - public int getSpi(int direction) { - return flow[direction].spi; + public int getSpiResourceId(int direction) { + return flow[direction].spiResourceId; } public InetAddress getRemoteAddress() { @@ -93,8 +93,8 @@ public final class IpSecConfig implements Parcelable { return encapType; } - public int getEncapLocalPort() { - return encapLocalPort; + public int getEncapLocalResourceId() { + return encapLocalPortResourceId; } public int getEncapRemotePort() { @@ -119,14 +119,14 @@ public final class IpSecConfig implements Parcelable { // TODO: Use a byte array or other better method for storing IPs that can also include scope out.writeString((remoteAddress != null) ? remoteAddress.getHostAddress() : null); out.writeParcelable(network, flags); - out.writeInt(flow[IpSecTransform.DIRECTION_IN].spi); + out.writeInt(flow[IpSecTransform.DIRECTION_IN].spiResourceId); out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryption, flags); out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authentication, flags); - out.writeInt(flow[IpSecTransform.DIRECTION_OUT].spi); + out.writeInt(flow[IpSecTransform.DIRECTION_OUT].spiResourceId); out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryption, flags); out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authentication, flags); out.writeInt(encapType); - out.writeInt(encapLocalPort); + out.writeInt(encapLocalPortResourceId); out.writeInt(encapRemotePort); } @@ -151,18 +151,18 @@ public final class IpSecConfig implements Parcelable { localAddress = readInetAddressFromParcel(in); remoteAddress = readInetAddressFromParcel(in); network = (Network) in.readParcelable(Network.class.getClassLoader()); - flow[IpSecTransform.DIRECTION_IN].spi = in.readInt(); + flow[IpSecTransform.DIRECTION_IN].spiResourceId = in.readInt(); flow[IpSecTransform.DIRECTION_IN].encryption = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); flow[IpSecTransform.DIRECTION_IN].authentication = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - flow[IpSecTransform.DIRECTION_OUT].spi = in.readInt(); + flow[IpSecTransform.DIRECTION_OUT].spiResourceId = in.readInt(); flow[IpSecTransform.DIRECTION_OUT].encryption = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); flow[IpSecTransform.DIRECTION_OUT].authentication = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); encapType = in.readInt(); - encapLocalPort = in.readInt(); + encapLocalPortResourceId = in.readInt(); encapRemotePort = in.readInt(); } diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 114e46e54fe7..0240cf15095e 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -19,10 +19,10 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.NonNull; import android.os.Binder; -import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.AndroidException; +import android.util.Log; import dalvik.system.CloseGuard; import java.io.FileDescriptor; import java.io.IOException; @@ -58,12 +58,6 @@ public final class IpSecManager { } /** @hide */ - public static final String KEY_STATUS = "status"; - /** @hide */ - public static final String KEY_RESOURCE_ID = "resourceId"; - /** @hide */ - public static final String KEY_SPI = "spi"; - /** @hide */ public static final int INVALID_RESOURCE_ID = 0; /** @@ -128,7 +122,11 @@ public final class IpSecManager { */ @Override public void close() { - mSpi = INVALID_SECURITY_PARAMETER_INDEX; + try { + mService.releaseSecurityParameterIndex(mResourceId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } mCloseGuard.close(); } @@ -147,7 +145,7 @@ public final class IpSecManager { mService = service; mRemoteAddress = remoteAddress; try { - Bundle result = + IpSecSpiResponse result = mService.reserveSecurityParameterIndex( direction, remoteAddress.getHostAddress(), spi, new Binder()); @@ -155,7 +153,7 @@ public final class IpSecManager { throw new NullPointerException("Received null response from IpSecService"); } - int status = result.getInt(KEY_STATUS); + int status = result.status; switch (status) { case Status.OK: break; @@ -168,8 +166,8 @@ public final class IpSecManager { throw new RuntimeException( "Unknown status returned by IpSecService: " + status); } - mSpi = result.getInt(KEY_SPI); - mResourceId = result.getInt(KEY_RESOURCE_ID); + mSpi = result.spi; + mResourceId = result.resourceId; if (mSpi == INVALID_SECURITY_PARAMETER_INDEX) { throw new RuntimeException("Invalid SPI returned by IpSecService: " + status); @@ -185,6 +183,11 @@ public final class IpSecManager { } mCloseGuard.open("open"); } + + /** @hide */ + int getResourceId() { + return mResourceId; + } } /** @@ -201,8 +204,7 @@ public final class IpSecManager { * @throws SpiUnavailableException indicating that a particular SPI cannot be reserved */ public SecurityParameterIndex reserveSecurityParameterIndex( - int direction, InetAddress remoteAddress) - throws ResourceUnavailableException { + int direction, InetAddress remoteAddress) throws ResourceUnavailableException { try { return new SecurityParameterIndex( mService, @@ -251,7 +253,9 @@ public final class IpSecManager { */ public void applyTransportModeTransform(Socket socket, IpSecTransform transform) throws IOException { - applyTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform); + try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) { + applyTransportModeTransform(pfd, transform); + } } /** @@ -269,15 +273,8 @@ public final class IpSecManager { */ public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform) throws IOException { - applyTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform); - } - - /* Call down to activate a transform */ - private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { - try { - mService.applyTransportModeTransform(pfd, transform.getResourceId()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) { + applyTransportModeTransform(pfd, transform); } } @@ -295,7 +292,22 @@ public final class IpSecManager { */ public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform) throws IOException { - applyTransportModeTransform(new ParcelFileDescriptor(socket), transform); + // We dup() the FileDescriptor here because if we don't, then the ParcelFileDescriptor() + // constructor takes control and closes the user's FD when we exit the method + // This is behaviorally the same as the other versions, but the PFD constructor does not + // dup() automatically, whereas PFD.fromSocket() and PDF.fromDatagramSocket() do dup(). + try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) { + applyTransportModeTransform(pfd, transform); + } + } + + /* Call down to activate a transform */ + private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { + try { + mService.applyTransportModeTransform(pfd, transform.getResourceId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -324,7 +336,9 @@ public final class IpSecManager { */ public void removeTransportModeTransform(Socket socket, IpSecTransform transform) throws IOException { - removeTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform); + try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) { + removeTransportModeTransform(pfd, transform); + } } /** @@ -340,7 +354,9 @@ public final class IpSecManager { */ public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform) throws IOException { - removeTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform); + try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) { + removeTransportModeTransform(pfd, transform); + } } /** @@ -355,7 +371,9 @@ public final class IpSecManager { */ public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform) throws IOException { - removeTransportModeTransform(new ParcelFileDescriptor(socket), transform); + try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) { + removeTransportModeTransform(pfd, transform); + } } /* Call down to activate a transform */ @@ -388,33 +406,48 @@ public final class IpSecManager { * FileDescriptor. Instead, disposing of this socket requires a call to close(). */ public static final class UdpEncapsulationSocket implements AutoCloseable { - private final FileDescriptor mFd; + private final ParcelFileDescriptor mPfd; private final IIpSecService mService; + private final int mResourceId; + private final int mPort; private final CloseGuard mCloseGuard = CloseGuard.get(); private UdpEncapsulationSocket(@NonNull IIpSecService service, int port) - throws ResourceUnavailableException { - mService = service; - mCloseGuard.open("constructor"); - // TODO: go down to the kernel and get a socket on the specified - mFd = new FileDescriptor(); - } - - private UdpEncapsulationSocket(IIpSecService service) throws ResourceUnavailableException { + throws ResourceUnavailableException, IOException { mService = service; + try { + IpSecUdpEncapResponse result = + mService.openUdpEncapsulationSocket(port, new Binder()); + switch (result.status) { + case Status.OK: + break; + case Status.RESOURCE_UNAVAILABLE: + throw new ResourceUnavailableException( + "No more Sockets may be allocated by this requester."); + default: + throw new RuntimeException( + "Unknown status returned by IpSecService: " + result.status); + } + mResourceId = result.resourceId; + mPort = result.port; + mPfd = result.fileDescriptor; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } mCloseGuard.open("constructor"); - // TODO: go get a random socket on a random port - mFd = new FileDescriptor(); } /** Access the inner UDP Encapsulation Socket */ public FileDescriptor getSocket() { - return mFd; + if (mPfd == null) { + return null; + } + return mPfd.getFileDescriptor(); } /** Retrieve the port number of the inner encapsulation socket */ public int getPort() { - return 0; // TODO get the port number from the Socket; + return mPort; } @Override @@ -429,7 +462,18 @@ public final class IpSecManager { * @param fd a file descriptor previously returned as a UDP Encapsulation socket. */ public void close() throws IOException { - // TODO: Go close the socket + try { + mService.closeUdpEncapsulationSocket(mResourceId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + try { + mPfd.close(); + } catch (IOException e) { + Log.e(TAG, "Failed to close UDP Encapsulation Socket with Port= " + mPort); + throw e; + } mCloseGuard.close(); } @@ -438,9 +482,13 @@ public final class IpSecManager { if (mCloseGuard != null) { mCloseGuard.warnIfOpen(); } - close(); } + + /** @hide */ + int getResourceId() { + return mResourceId; + } }; /** @@ -467,7 +515,13 @@ public final class IpSecManager { // socket. public UdpEncapsulationSocket openUdpEncapsulationSocket(int port) throws IOException, ResourceUnavailableException { - // Temporary code + /* + * Most range checking is done in the service, but this version of the constructor expects + * a valid port number, and zero cannot be checked after being passed to the service. + */ + if (port == 0) { + throw new IllegalArgumentException("Specified port must be a valid port number!"); + } return new UdpEncapsulationSocket(mService, port); } @@ -491,8 +545,7 @@ public final class IpSecManager { // socket. public UdpEncapsulationSocket openUdpEncapsulationSocket() throws IOException, ResourceUnavailableException { - // Temporary code - return new UdpEncapsulationSocket(mService); + return new UdpEncapsulationSocket(mService, 0); } /** diff --git a/core/java/android/net/IpSecSpiResponse.aidl b/core/java/android/net/IpSecSpiResponse.aidl new file mode 100644 index 000000000000..6484a0013c53 --- /dev/null +++ b/core/java/android/net/IpSecSpiResponse.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +/** @hide */ +parcelable IpSecSpiResponse; diff --git a/core/java/android/net/IpSecSpiResponse.java b/core/java/android/net/IpSecSpiResponse.java new file mode 100644 index 000000000000..71dfa03ac112 --- /dev/null +++ b/core/java/android/net/IpSecSpiResponse.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class is used to return an SPI and corresponding status from the IpSecService to an + * IpSecManager.SecurityParameterIndex. + * + * @hide + */ +public final class IpSecSpiResponse implements Parcelable { + private static final String TAG = "IpSecSpiResponse"; + + public final int resourceId; + public final int status; + public final int spi; + // Parcelable Methods + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(status); + out.writeInt(resourceId); + out.writeInt(spi); + } + + public IpSecSpiResponse(int inStatus, int inResourceId, int inSpi) { + status = inStatus; + resourceId = inResourceId; + spi = inSpi; + } + + public IpSecSpiResponse(int inStatus) { + if (inStatus == IpSecManager.Status.OK) { + throw new IllegalArgumentException("Valid status implies other args must be provided"); + } + status = inStatus; + resourceId = IpSecManager.INVALID_RESOURCE_ID; + spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX; + } + + private IpSecSpiResponse(Parcel in) { + status = in.readInt(); + resourceId = in.readInt(); + spi = in.readInt(); + } + + public static final Parcelable.Creator<IpSecSpiResponse> CREATOR = + new Parcelable.Creator<IpSecSpiResponse>() { + public IpSecSpiResponse createFromParcel(Parcel in) { + return new IpSecSpiResponse(in); + } + + public IpSecSpiResponse[] newArray(int size) { + return new IpSecSpiResponse[size]; + } + }; +} diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java index 639d1f2689f4..e65f534f3dc6 100644 --- a/core/java/android/net/IpSecTransform.java +++ b/core/java/android/net/IpSecTransform.java @@ -16,15 +16,12 @@ package android.net; import static android.net.IpSecManager.INVALID_RESOURCE_ID; -import static android.net.IpSecManager.KEY_RESOURCE_ID; -import static android.net.IpSecManager.KEY_STATUS; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.content.Context; import android.os.Binder; -import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; @@ -78,23 +75,23 @@ public final class IpSecTransform implements AutoCloseable { public static final int ENCAP_NONE = 0; /** - * IpSec traffic will be encapsulated within UDP as per <a - * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>. + * IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad + * (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP. * * @hide */ - public static final int ENCAP_ESPINUDP = 1; + public static final int ENCAP_ESPINUDP_NON_IKE = 1; /** - * IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad - * (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP. + * IpSec traffic will be encapsulated within UDP as per <a + * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>. * * @hide */ - public static final int ENCAP_ESPINUDP_NONIKE = 2; + public static final int ENCAP_ESPINUDP = 2; /** @hide */ - @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NONIKE}) + @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NON_IKE}) @Retention(RetentionPolicy.SOURCE) public @interface EncapType {} @@ -139,10 +136,11 @@ public final class IpSecTransform implements AutoCloseable { synchronized (this) { try { IIpSecService svc = getIpSecService(); - Bundle result = svc.createTransportModeTransform(mConfig, new Binder()); - int status = result.getInt(KEY_STATUS); + IpSecTransformResponse result = + svc.createTransportModeTransform(mConfig, new Binder()); + int status = result.status; checkResultStatusAndThrow(status); - mResourceId = result.getInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID); + mResourceId = result.resourceId; /* Keepalive will silently fail if not needed by the config; but, if needed and * it fails to start, we need to bail because a transform will not be reliable @@ -263,7 +261,10 @@ public final class IpSecTransform implements AutoCloseable { mConfig.getNattKeepaliveInterval(), mKeepaliveCallback, mConfig.getLocalAddress(), - mConfig.getEncapLocalPort(), + 0x1234, /* FIXME: get the real port number again, + which we need to retrieve from the provided + EncapsulationSocket, and which isn't currently + stashed in IpSecConfig */ mConfig.getRemoteAddress()); try { // FIXME: this is still a horrible way to fudge the synchronous callback @@ -360,7 +361,7 @@ public final class IpSecTransform implements AutoCloseable { @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) { // TODO: convert to using the resource Id of the SPI. Then build() can validate // the owner in the IpSecService - mConfig.flow[direction].spi = spi.getSpi(); + mConfig.flow[direction].spiResourceId = spi.getResourceId(); return this; } @@ -394,7 +395,7 @@ public final class IpSecTransform implements AutoCloseable { IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { // TODO: check encap type is valid. mConfig.encapType = ENCAP_ESPINUDP; - mConfig.encapLocalPort = localSocket.getPort(); // TODO: plug in the encap socket + mConfig.encapLocalPortResourceId = localSocket.getResourceId(); mConfig.encapRemotePort = remotePort; return this; } diff --git a/core/java/android/net/IpSecTransformResponse.aidl b/core/java/android/net/IpSecTransformResponse.aidl new file mode 100644 index 000000000000..546230d5b888 --- /dev/null +++ b/core/java/android/net/IpSecTransformResponse.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +/** @hide */ +parcelable IpSecTransformResponse; diff --git a/core/java/android/net/IpSecTransformResponse.java b/core/java/android/net/IpSecTransformResponse.java new file mode 100644 index 000000000000..cfc176227fbc --- /dev/null +++ b/core/java/android/net/IpSecTransformResponse.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class is used to return an IpSecTransform resource Id and and corresponding status from the + * IpSecService to an IpSecTransform object. + * + * @hide + */ +public final class IpSecTransformResponse implements Parcelable { + private static final String TAG = "IpSecTransformResponse"; + + public final int resourceId; + public final int status; + // Parcelable Methods + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(status); + out.writeInt(resourceId); + } + + public IpSecTransformResponse(int inStatus) { + if (inStatus == IpSecManager.Status.OK) { + throw new IllegalArgumentException("Valid status implies other args must be provided"); + } + status = inStatus; + resourceId = IpSecManager.INVALID_RESOURCE_ID; + } + + public IpSecTransformResponse(int inStatus, int inResourceId) { + status = inStatus; + resourceId = inResourceId; + } + + private IpSecTransformResponse(Parcel in) { + status = in.readInt(); + resourceId = in.readInt(); + } + + public static final Parcelable.Creator<IpSecTransformResponse> CREATOR = + new Parcelable.Creator<IpSecTransformResponse>() { + public IpSecTransformResponse createFromParcel(Parcel in) { + return new IpSecTransformResponse(in); + } + + public IpSecTransformResponse[] newArray(int size) { + return new IpSecTransformResponse[size]; + } + }; +} diff --git a/core/java/android/net/IpSecUdpEncapResponse.aidl b/core/java/android/net/IpSecUdpEncapResponse.aidl new file mode 100644 index 000000000000..5e451f3651f1 --- /dev/null +++ b/core/java/android/net/IpSecUdpEncapResponse.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +/** @hide */ +parcelable IpSecUdpEncapResponse; diff --git a/core/java/android/net/IpSecUdpEncapResponse.java b/core/java/android/net/IpSecUdpEncapResponse.java new file mode 100644 index 000000000000..4679267cf9a9 --- /dev/null +++ b/core/java/android/net/IpSecUdpEncapResponse.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.net; + +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; +import java.io.FileDescriptor; +import java.io.IOException; + +/** + * This class is used to return a UDP Socket and corresponding status from the IpSecService to an + * IpSecManager.UdpEncapsulationSocket. + * + * @hide + */ +public final class IpSecUdpEncapResponse implements Parcelable { + private static final String TAG = "IpSecUdpEncapResponse"; + + public final int resourceId; + public final int port; + public final int status; + // There is a weird asymmetry with FileDescriptor: you can write a FileDescriptor + // but you read a ParcelFileDescriptor. To circumvent this, when we receive a FD + // from the user, we immediately create a ParcelFileDescriptor DUP, which we invalidate + // on writeParcel() by setting the flag to do close-on-write. + // TODO: tests to ensure this doesn't leak + public final ParcelFileDescriptor fileDescriptor; + + // Parcelable Methods + + @Override + public int describeContents() { + return (fileDescriptor != null) ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(status); + out.writeInt(resourceId); + out.writeInt(port); + out.writeParcelable(fileDescriptor, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + } + + public IpSecUdpEncapResponse(int inStatus) { + if (inStatus == IpSecManager.Status.OK) { + throw new IllegalArgumentException("Valid status implies other args must be provided"); + } + status = inStatus; + resourceId = IpSecManager.INVALID_RESOURCE_ID; + port = -1; + fileDescriptor = null; // yes I know it's redundant, but readability + } + + public IpSecUdpEncapResponse(int inStatus, int inResourceId, int inPort, FileDescriptor inFd) + throws IOException { + if (inStatus == IpSecManager.Status.OK && inFd == null) { + throw new IllegalArgumentException("Valid status implies FD must be non-null"); + } + status = inStatus; + resourceId = inResourceId; + port = inPort; + fileDescriptor = (status == IpSecManager.Status.OK) ? ParcelFileDescriptor.dup(inFd) : null; + } + + private IpSecUdpEncapResponse(Parcel in) { + status = in.readInt(); + resourceId = in.readInt(); + port = in.readInt(); + fileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader()); + } + + public static final Parcelable.Creator<IpSecUdpEncapResponse> CREATOR = + new Parcelable.Creator<IpSecUdpEncapResponse>() { + public IpSecUdpEncapResponse createFromParcel(Parcel in) { + return new IpSecUdpEncapResponse(in); + } + + public IpSecUdpEncapResponse[] newArray(int size) { + return new IpSecUdpEncapResponse[size]; + } + }; +} diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index a7ce95ba0c88..ec275cc6f77d 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -18,9 +18,10 @@ package com.android.server; import static android.Manifest.permission.DUMP; import static android.net.IpSecManager.INVALID_RESOURCE_ID; -import static android.net.IpSecManager.KEY_RESOURCE_ID; -import static android.net.IpSecManager.KEY_SPI; -import static android.net.IpSecManager.KEY_STATUS; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.SOCK_DGRAM; +import static com.android.internal.util.Preconditions.checkNotNull; import android.content.Context; import android.net.IIpSecService; @@ -28,47 +29,92 @@ import android.net.INetd; import android.net.IpSecAlgorithm; import android.net.IpSecConfig; import android.net.IpSecManager; +import android.net.IpSecSpiResponse; import android.net.IpSecTransform; +import android.net.IpSecTransformResponse; +import android.net.IpSecUdpEncapResponse; import android.net.util.NetdService; import android.os.Binder; -import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceSpecificException; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; import android.util.Log; import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import java.io.FileDescriptor; +import java.io.IOException; import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; import java.util.concurrent.atomic.AtomicInteger; +import libcore.io.IoUtils; /** @hide */ public class IpSecService extends IIpSecService.Stub { private static final String TAG = "IpSecService"; private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); + private static final String NETD_SERVICE_NAME = "netd"; private static final int[] DIRECTIONS = new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN}; - /** Binder context for this service */ + private static final int NETD_FETCH_TIMEOUT = 5000; //ms + private static final int MAX_PORT_BIND_ATTEMPTS = 10; + private static final InetAddress INADDR_ANY; + + static { + try { + INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0}); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + static final int FREE_PORT_MIN = 1024; // ports 1-1023 are reserved + static final int PORT_MAX = 0xFFFF; // ports are an unsigned 16-bit integer + + /* Binder context for this service */ private final Context mContext; - private Object mLock = new Object(); + /** Should be a never-repeating global ID for resources */ + private static AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0); - private static final int NETD_FETCH_TIMEOUT = 5000; //ms + @GuardedBy("this") + private final ManagedResourceArray<SpiRecord> mSpiRecords = new ManagedResourceArray<>(); + + @GuardedBy("this") + private final ManagedResourceArray<TransformRecord> mTransformRecords = + new ManagedResourceArray<>(); - private AtomicInteger mNextResourceId = new AtomicInteger(0x00FADED0); + @GuardedBy("this") + private final ManagedResourceArray<UdpSocketRecord> mUdpSocketRecords = + new ManagedResourceArray<>(); + /** + * The ManagedResource class provides a facility to cleanly and reliably release system + * resources. It relies on two things: an IBinder that allows ManagedResource to automatically + * clean up in the event that the Binder dies and a user-provided resourceId that should + * uniquely identify the managed resource. To use this class, the user should implement the + * releaseResources() method that is responsible for releasing system resources when invoked. + */ private abstract class ManagedResource implements IBinder.DeathRecipient { final int pid; final int uid; private IBinder mBinder; + protected int mResourceId; + + private AtomicInteger mReferenceCount = new AtomicInteger(0); - ManagedResource(IBinder binder) { + ManagedResource(int resourceId, IBinder binder) { super(); mBinder = binder; + mResourceId = resourceId; pid = Binder.getCallingPid(); uid = Binder.getCallingUid(); @@ -79,21 +125,53 @@ public class IpSecService extends IIpSecService.Stub { } } + public void addReference() { + mReferenceCount.incrementAndGet(); + } + + public void removeReference() { + if (mReferenceCount.decrementAndGet() < 0) { + Log.wtf(TAG, "Programming error: negative reference count"); + } + } + + public boolean isReferenced() { + return (mReferenceCount.get() > 0); + } + + public void checkOwnerOrSystemAndThrow() { + if (uid != Binder.getCallingUid() + && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) { + throw new SecurityException("Only the owner may access managed resources!"); + } + } + /** * When this record is no longer needed for managing system resources this function should - * unlink all references held by the record to allow efficient garbage collection. + * clean up all system resources and nullify the record. This function shall perform all + * necessary cleanup of the resources managed by this record. + * + * <p>NOTE: this function verifies ownership before allowing resources to be freed. */ - public final void release() { - //Release all the underlying system resources first - releaseResources(); + public final void release() throws RemoteException { + synchronized (IpSecService.this) { + if (isReferenced()) { + throw new IllegalStateException( + "Cannot release a resource that has active references!"); + } - if (mBinder != null) { - mBinder.unlinkToDeath(this, 0); - } - mBinder = null; + if (mResourceId == INVALID_RESOURCE_ID) { + return; + } + + releaseResources(); + if (mBinder != null) { + mBinder.unlinkToDeath(this, 0); + } + mBinder = null; - //remove this record so that it can be cleaned up - nullifyRecord(); + mResourceId = INVALID_RESOURCE_ID; + } } /** @@ -102,41 +180,85 @@ public class IpSecService extends IIpSecService.Stub { * collection */ public final void binderDied() { - release(); + try { + release(); + } catch (Exception e) { + Log.e(TAG, "Failed to release resource: " + e); + } } /** - * Implement this method to release all object references contained in the subclass to allow - * efficient garbage collection of the record. This should remove any references to the - * record from all other locations that hold a reference as the record is no longer valid. - */ - protected abstract void nullifyRecord(); - - /** * Implement this method to release all system resources that are being protected by this * record. Once the resources are released, the record should be invalidated and no longer - * used by calling releaseRecord() + * used by calling release(). This should NEVER be called directly. + * + * <p>Calls to this are always guarded by IpSecService#this */ - protected abstract void releaseResources(); + protected abstract void releaseResources() throws RemoteException; }; + /** + * Minimal wrapper around SparseArray that performs ownership + * validation on element accesses. + */ + private class ManagedResourceArray<T extends ManagedResource> { + SparseArray<T> mArray = new SparseArray<>(); + + T get(int key) { + T val = mArray.get(key); + val.checkOwnerOrSystemAndThrow(); + return val; + } + + void put(int key, T obj) { + checkNotNull(obj, "Null resources cannot be added"); + mArray.put(key, obj); + } + + void remove(int key) { + mArray.remove(key); + } + } + private final class TransformRecord extends ManagedResource { - private IpSecConfig mConfig; - private int mResourceId; + private final IpSecConfig mConfig; + private final SpiRecord[] mSpis; + private final UdpSocketRecord mSocket; - TransformRecord(IpSecConfig config, int resourceId, IBinder binder) { - super(binder); + TransformRecord( + int resourceId, + IBinder binder, + IpSecConfig config, + SpiRecord[] spis, + UdpSocketRecord socket) { + super(resourceId, binder); mConfig = config; - mResourceId = resourceId; + mSpis = spis; + mSocket = socket; + + for (int direction : DIRECTIONS) { + mSpis[direction].addReference(); + mSpis[direction].setOwnedByTransform(); + } + + if (mSocket != null) { + mSocket.addReference(); + } } public IpSecConfig getConfig() { return mConfig; } + public SpiRecord getSpiRecord(int direction) { + return mSpis[direction]; + } + + /** always guarded by IpSecService#this */ @Override protected void releaseResources() { for (int direction : DIRECTIONS) { + int spi = mSpis[direction].getSpi(); try { getNetdInstance() .ipSecDeleteSecurityAssociation( @@ -148,19 +270,21 @@ public class IpSecService extends IIpSecService.Stub { (mConfig.getRemoteAddress() != null) ? mConfig.getRemoteAddress().getHostAddress() : "", - mConfig.getSpi(direction)); + spi); } catch (ServiceSpecificException e) { // FIXME: get the error code and throw is at an IOException from Errno Exception } catch (RemoteException e) { Log.e(TAG, "Failed to delete SA with ID: " + mResourceId); } } - } - @Override - protected void nullifyRecord() { - mConfig = null; - mResourceId = INVALID_RESOURCE_ID; + for (int direction : DIRECTIONS) { + mSpis[direction].removeReference(); + } + + if (mSocket != null) { + mSocket.removeReference(); + } } } @@ -168,27 +292,37 @@ public class IpSecService extends IIpSecService.Stub { private final int mDirection; private final String mLocalAddress; private final String mRemoteAddress; - private final IBinder mBinder; private int mSpi; - private int mResourceId; + + private boolean mOwnedByTransform = false; SpiRecord( int resourceId, + IBinder binder, int direction, String localAddress, String remoteAddress, - int spi, - IBinder binder) { - super(binder); - mResourceId = resourceId; + int spi) { + super(resourceId, binder); mDirection = direction; mLocalAddress = localAddress; mRemoteAddress = remoteAddress; mSpi = spi; - mBinder = binder; } + /** always guarded by IpSecService#this */ + @Override protected void releaseResources() { + if (mOwnedByTransform) { + Log.d(TAG, "Cannot release Spi " + mSpi + ": Currently locked by a Transform"); + // Because SPIs are "handed off" to transform, objects, they should never be + // freed from the SpiRecord once used in a transform. (They refer to the same SA, + // thus ownership and responsibility for freeing these resources passes to the + // Transform object). Thus, we should let the user free them without penalty once + // they are applied in a Transform object. + return; + } + try { getNetdInstance() .ipSecDeleteSecurityAssociation( @@ -198,19 +332,50 @@ public class IpSecService extends IIpSecService.Stub { } catch (RemoteException e) { Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId); } - } - protected void nullifyRecord() { mSpi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX; - mResourceId = INVALID_RESOURCE_ID; + } + + public int getSpi() { + return mSpi; + } + + public void setOwnedByTransform() { + if (mOwnedByTransform) { + // Programming error + new IllegalStateException("Cannot own an SPI twice!"); + } + + mOwnedByTransform = true; } } - @GuardedBy("mSpiRecords") - private final SparseArray<SpiRecord> mSpiRecords = new SparseArray<>(); + private final class UdpSocketRecord extends ManagedResource { + private FileDescriptor mSocket; + private final int mPort; + + UdpSocketRecord(int resourceId, IBinder binder, FileDescriptor socket, int port) { + super(resourceId, binder); + mSocket = socket; + mPort = port; + } + + /** always guarded by IpSecService#this */ + @Override + protected void releaseResources() { + Log.d(TAG, "Closing port " + mPort); + IoUtils.closeQuietly(mSocket); + mSocket = null; + } + + public int getPort() { + return mPort; + } - @GuardedBy("mTransformRecords") - private final SparseArray<TransformRecord> mTransformRecords = new SparseArray<>(); + public FileDescriptor getSocket() { + return mSocket; + } + } /** * Constructs a new IpSecService instance @@ -242,7 +407,7 @@ public class IpSecService extends IIpSecService.Stub { new Runnable() { @Override public void run() { - synchronized (mLock) { + synchronized (IpSecService.this) { NetdService.get(NETD_FETCH_TIMEOUT); } } @@ -258,30 +423,27 @@ public class IpSecService extends IIpSecService.Stub { return netd; } - boolean isNetdAlive() { - synchronized (mLock) { - try { - final INetd netd = getNetdInstance(); - if (netd == null) { - return false; - } - return netd.isAlive(); - } catch (RemoteException re) { + synchronized boolean isNetdAlive() { + try { + final INetd netd = getNetdInstance(); + if (netd == null) { return false; } + return netd.isAlive(); + } catch (RemoteException re) { + return false; } } @Override /** Get a new SPI and maintain the reservation in the system server */ - public Bundle reserveSecurityParameterIndex( + public synchronized IpSecSpiResponse reserveSecurityParameterIndex( int direction, String remoteAddress, int requestedSpi, IBinder binder) throws RemoteException { int resourceId = mNextResourceId.getAndIncrement(); int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX; String localAddress = ""; - Bundle retBundle = new Bundle(3); try { spi = getNetdInstance() @@ -292,29 +454,78 @@ public class IpSecService extends IIpSecService.Stub { remoteAddress, requestedSpi); Log.d(TAG, "Allocated SPI " + spi); - retBundle.putInt(KEY_STATUS, IpSecManager.Status.OK); - retBundle.putInt(KEY_RESOURCE_ID, resourceId); - retBundle.putInt(KEY_SPI, spi); - synchronized (mSpiRecords) { - mSpiRecords.put( - resourceId, - new SpiRecord( - resourceId, direction, localAddress, remoteAddress, spi, binder)); - } + mSpiRecords.put( + resourceId, + new SpiRecord(resourceId, binder, direction, localAddress, remoteAddress, spi)); } catch (ServiceSpecificException e) { // TODO: Add appropriate checks when other ServiceSpecificException types are supported - retBundle.putInt(KEY_STATUS, IpSecManager.Status.SPI_UNAVAILABLE); - retBundle.putInt(KEY_RESOURCE_ID, resourceId); - retBundle.putInt(KEY_SPI, spi); + return new IpSecSpiResponse( + IpSecManager.Status.SPI_UNAVAILABLE, IpSecManager.INVALID_RESOURCE_ID, spi); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - return retBundle; + return new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, spi); + } + + /* This method should only be called from Binder threads. Do not call this from + * within the system server as it will crash the system on failure. + */ + private synchronized <T extends ManagedResource> void releaseManagedResource( + ManagedResourceArray<T> resArray, int resourceId, String typeName) + throws RemoteException { + // We want to non-destructively get so that we can check credentials before removing + // this from the records. + T record = resArray.get(resourceId); + + if (record == null) { + throw new IllegalArgumentException( + typeName + " " + resourceId + " is not available to be deleted"); + } + + record.release(); + resArray.remove(resourceId); } /** Release a previously allocated SPI that has been registered with the system server */ @Override - public void releaseSecurityParameterIndex(int resourceId) throws RemoteException {} + public void releaseSecurityParameterIndex(int resourceId) throws RemoteException { + releaseManagedResource(mSpiRecords, resourceId, "SecurityParameterIndex"); + } + + /** + * This function finds and forcibly binds to a random system port, ensuring that the port cannot + * be unbound. + * + * <p>A socket cannot be un-bound from a port if it was bound to that port by number. To select + * a random open port and then bind by number, this function creates a temp socket, binds to a + * random port (specifying 0), gets that port number, and then uses is to bind the user's UDP + * Encapsulation Socket forcibly, so that it cannot be un-bound by the user with the returned + * FileHandle. + * + * <p>The loop in this function handles the inherent race window between un-binding to a port + * and re-binding, during which the system could *technically* hand that port out to someone + * else. + */ + private void bindToRandomPort(FileDescriptor sockFd) throws IOException { + for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) { + try { + FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + Os.bind(probeSocket, INADDR_ANY, 0); + int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort(); + Os.close(probeSocket); + Log.v(TAG, "Binding to port " + port); + Os.bind(sockFd, INADDR_ANY, port); + return; + } catch (ErrnoException e) { + // Someone miraculously claimed the port just after we closed probeSocket. + if (e.errno == OsConstants.EADDRINUSE) { + continue; + } + throw e.rethrowAsIOException(); + } + } + throw new IOException("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port"); + } /** * Open a socket via the system server and bind it to the specified port (random if port=0). @@ -323,13 +534,47 @@ public class IpSecService extends IIpSecService.Stub { * needed. */ @Override - public Bundle openUdpEncapsulationSocket(int port, IBinder binder) throws RemoteException { - return null; + public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder) + throws RemoteException { + if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) { + throw new IllegalArgumentException( + "Specified port number must be a valid non-reserved UDP port"); + } + int resourceId = mNextResourceId.getAndIncrement(); + FileDescriptor sockFd = null; + try { + sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + if (port != 0) { + Log.v(TAG, "Binding to port " + port); + Os.bind(sockFd, INADDR_ANY, port); + } else { + bindToRandomPort(sockFd); + } + // This code is common to both the unspecified and specified port cases + Os.setsockoptInt( + sockFd, + OsConstants.IPPROTO_UDP, + OsConstants.UDP_ENCAP, + OsConstants.UDP_ENCAP_ESPINUDP); + + mUdpSocketRecords.put( + resourceId, new UdpSocketRecord(resourceId, binder, sockFd, port)); + return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd); + } catch (IOException | ErrnoException e) { + IoUtils.closeQuietly(sockFd); + } + // If we make it to here, then something has gone wrong and we couldn't open a socket. + // The only reasonable condition that would cause that is resource unavailable. + return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); } /** close a socket that has been been allocated by and registered with the system server */ @Override - public void closeUdpEncapsulationSocket(ParcelFileDescriptor socket) {} + public void closeUdpEncapsulationSocket(int resourceId) throws RemoteException { + + releaseManagedResource(mUdpSocketRecords, resourceId, "UdpEncapsulationSocket"); + } /** * Create a transport mode transform, which represent two security associations (one in each @@ -339,13 +584,26 @@ public class IpSecService extends IIpSecService.Stub { * receive data. */ @Override - public Bundle createTransportModeTransform(IpSecConfig c, IBinder binder) - throws RemoteException { - // TODO: Basic input validation here since it's coming over the Binder + public synchronized IpSecTransformResponse createTransportModeTransform( + IpSecConfig c, IBinder binder) throws RemoteException { int resourceId = mNextResourceId.getAndIncrement(); + SpiRecord[] spis = new SpiRecord[DIRECTIONS.length]; + // TODO: Basic input validation here since it's coming over the Binder + int encapType, encapLocalPort = 0, encapRemotePort = 0; + UdpSocketRecord socketRecord = null; + encapType = c.getEncapType(); + if (encapType != IpSecTransform.ENCAP_NONE) { + socketRecord = mUdpSocketRecords.get(c.getEncapLocalResourceId()); + encapLocalPort = socketRecord.getPort(); + encapRemotePort = c.getEncapRemotePort(); + } + for (int direction : DIRECTIONS) { IpSecAlgorithm auth = c.getAuthentication(direction); IpSecAlgorithm crypt = c.getEncryption(direction); + + spis[direction] = mSpiRecords.get(c.getSpiResourceId(direction)); + int spi = spis[direction].getSpi(); try { int result = getNetdInstance() @@ -362,35 +620,29 @@ public class IpSecService extends IIpSecService.Stub { (c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0, - c.getSpi(direction), + spi, (auth != null) ? auth.getName() : "", (auth != null) ? auth.getKey() : null, (auth != null) ? auth.getTruncationLengthBits() : 0, (crypt != null) ? crypt.getName() : "", (crypt != null) ? crypt.getKey() : null, (crypt != null) ? crypt.getTruncationLengthBits() : 0, - c.getEncapType(), - c.getEncapLocalPort(), - c.getEncapRemotePort()); - if (result != c.getSpi(direction)) { + encapType, + encapLocalPort, + encapRemotePort); + if (result != spi) { // TODO: cleanup the first SA if creation of second SA fails - Bundle retBundle = new Bundle(2); - retBundle.putInt(KEY_STATUS, IpSecManager.Status.SPI_UNAVAILABLE); - retBundle.putInt(KEY_RESOURCE_ID, INVALID_RESOURCE_ID); - return retBundle; + return new IpSecTransformResponse( + IpSecManager.Status.SPI_UNAVAILABLE, INVALID_RESOURCE_ID); } } catch (ServiceSpecificException e) { // FIXME: get the error code and throw is at an IOException from Errno Exception } } - synchronized (mTransformRecords) { - mTransformRecords.put(resourceId, new TransformRecord(c, resourceId, binder)); - } - - Bundle retBundle = new Bundle(2); - retBundle.putInt(KEY_STATUS, IpSecManager.Status.OK); - retBundle.putInt(KEY_RESOURCE_ID, resourceId); - return retBundle; + // Both SAs were created successfully, time to construct a record and lock it away + mTransformRecords.put( + resourceId, new TransformRecord(resourceId, binder, c, spis, socketRecord)); + return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId); } /** @@ -401,27 +653,7 @@ public class IpSecService extends IIpSecService.Stub { */ @Override public void deleteTransportModeTransform(int resourceId) throws RemoteException { - synchronized (mTransformRecords) { - TransformRecord record; - // We want to non-destructively get so that we can check credentials before removing - // this from the records. - record = mTransformRecords.get(resourceId); - - if (record == null) { - throw new IllegalArgumentException( - "Transform " + resourceId + " is not available to be deleted"); - } - - if (record.pid != Binder.getCallingPid() || record.uid != Binder.getCallingUid()) { - throw new SecurityException("Only the owner of an IpSec Transform may delete it!"); - } - - // TODO: if releaseResources() throws RemoteException, we can try again to clean up on - // binder death. Need to make sure that path is actually functional. - record.releaseResources(); - mTransformRecords.remove(resourceId); - record.nullifyRecord(); - } + releaseManagedResource(mTransformRecords, resourceId, "IpSecTransform"); } /** @@ -429,44 +661,43 @@ public class IpSecService extends IIpSecService.Stub { * association as a correspondent policy to the provided socket */ @Override - public void applyTransportModeTransform(ParcelFileDescriptor socket, int resourceId) - throws RemoteException { - - synchronized (mTransformRecords) { - TransformRecord info; - // FIXME: this code should be factored out into a security check + getter - info = mTransformRecords.get(resourceId); - - if (info == null) { - throw new IllegalArgumentException("Transform " + resourceId + " is not active"); - } + public synchronized void applyTransportModeTransform( + ParcelFileDescriptor socket, int resourceId) throws RemoteException { + // Synchronize liberally here because we are using ManagedResources in this block + TransformRecord info; + // FIXME: this code should be factored out into a security check + getter + info = mTransformRecords.get(resourceId); + + if (info == null) { + throw new IllegalArgumentException("Transform " + resourceId + " is not active"); + } - // TODO: make this a function. - if (info.pid != getCallingPid() || info.uid != getCallingUid()) { - throw new SecurityException("Only the owner of an IpSec Transform may apply it!"); - } + // TODO: make this a function. + if (info.pid != getCallingPid() || info.uid != getCallingUid()) { + throw new SecurityException("Only the owner of an IpSec Transform may apply it!"); + } - IpSecConfig c = info.getConfig(); - try { - for (int direction : DIRECTIONS) { - getNetdInstance() - .ipSecApplyTransportModeTransform( - socket.getFileDescriptor(), - resourceId, - direction, - (c.getLocalAddress() != null) - ? c.getLocalAddress().getHostAddress() - : "", - (c.getRemoteAddress() != null) - ? c.getRemoteAddress().getHostAddress() - : "", - c.getSpi(direction)); - } - } catch (ServiceSpecificException e) { - // FIXME: get the error code and throw is at an IOException from Errno Exception + IpSecConfig c = info.getConfig(); + try { + for (int direction : DIRECTIONS) { + getNetdInstance() + .ipSecApplyTransportModeTransform( + socket.getFileDescriptor(), + resourceId, + direction, + (c.getLocalAddress() != null) + ? c.getLocalAddress().getHostAddress() + : "", + (c.getRemoteAddress() != null) + ? c.getRemoteAddress().getHostAddress() + : "", + info.getSpiRecord(direction).getSpi()); } + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception } } + /** * Remove a transport mode transform from a socket, applying the default (empty) policy. This * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of @@ -486,7 +717,7 @@ public class IpSecService extends IIpSecService.Stub { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { mContext.enforceCallingOrSelfPermission(DUMP, TAG); - + // TODO: Add dump code to print out a log of all the resources being tracked pw.println("IpSecService Log:"); pw.println("NetdNativeService Connection: " + (isNetdAlive() ? "alive" : "dead")); pw.println(); |