diff options
7 files changed, 469 insertions, 10 deletions
diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl index eeb30e23d000..3ce0283d7f23 100644 --- a/core/java/android/net/IIpSecService.aidl +++ b/core/java/android/net/IIpSecService.aidl @@ -21,6 +21,7 @@ import android.net.IpSecConfig;  import android.net.IpSecUdpEncapResponse;  import android.net.IpSecSpiResponse;  import android.net.IpSecTransformResponse; +import android.net.IpSecTunnelInterfaceResponse;  import android.os.Bundle;  import android.os.IBinder;  import android.os.ParcelFileDescriptor; @@ -39,11 +40,29 @@ interface IIpSecService      void closeUdpEncapsulationSocket(int resourceId); +    IpSecTunnelInterfaceResponse createTunnelInterface( +            in String localAddr, +            in String remoteAddr, +            in Network underlyingNetwork, +            in IBinder binder); + +    void addAddressToTunnelInterface( +            int tunnelResourceId, +            String localAddr); + +    void removeAddressFromTunnelInterface( +            int tunnelResourceId, +            String localAddr); + +    void deleteTunnelInterface(int resourceId); +      IpSecTransformResponse createTransform(in IpSecConfig c, in IBinder binder);      void deleteTransform(int transformId);      void applyTransportModeTransform(in ParcelFileDescriptor socket, int direction, int transformId); +    void applyTunnelModeTransform(int tunnelResourceId, int direction, int transformResourceId); +      void removeTransportModeTransforms(in ParcelFileDescriptor socket);  } diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 6125394ab5e3..24a078fccc1d 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -685,7 +685,30 @@ public final class IpSecManager {              mLocalAddress = localAddress;              mRemoteAddress = remoteAddress;              mUnderlyingNetwork = underlyingNetwork; -            // TODO: Call IpSecService + +            try { +                IpSecTunnelInterfaceResponse result = +                        mService.createTunnelInterface( +                                localAddress.getHostAddress(), +                                remoteAddress.getHostAddress(), +                                underlyingNetwork, +                                new Binder()); +                switch (result.status) { +                    case Status.OK: +                        break; +                    case Status.RESOURCE_UNAVAILABLE: +                        throw new ResourceUnavailableException( +                                "No more tunnel interfaces may be allocated by this requester."); +                    default: +                        throw new RuntimeException( +                                "Unknown status returned by IpSecService: " + result.status); +                } +                mResourceId = result.resourceId; +                mInterfaceName = result.interfaceName; +            } catch (RemoteException e) { +                throw e.rethrowFromSystemServer(); +            } +            mCloseGuard.open("constructor");          }          /** @@ -697,12 +720,12 @@ public final class IpSecManager {           */          @Override          public void close() { -            // try { -            // TODO: Call IpSecService -            mResourceId = INVALID_RESOURCE_ID; -            // } catch (RemoteException e) { -            //    throw e.rethrowFromSystemServer(); -            // } +            try { +                mService.deleteTunnelInterface(mResourceId); +                mResourceId = INVALID_RESOURCE_ID; +            } catch (RemoteException e) { +                throw e.rethrowFromSystemServer(); +            }              mCloseGuard.close();          } @@ -714,11 +737,20 @@ public final class IpSecManager {              }              close();          } + +        /** @hide */ +        @VisibleForTesting +        public int getResourceId() { +            return mResourceId; +        }      }      /**       * Create a new IpSecTunnelInterface as a local endpoint for tunneled IPsec traffic.       * +     * <p>An application that creates tunnels is responsible for cleaning up the tunnel when the +     * underlying network goes away, and the onLost() callback is received. +     *       * @param localAddress The local addres of the tunnel       * @param remoteAddress The local addres of the tunnel       * @param underlyingNetwork the {@link Network} that will carry traffic for this tunnel. @@ -750,7 +782,12 @@ public final class IpSecManager {      @SystemApi      public void applyTunnelModeTransform(IpSecTunnelInterface tunnel, int direction,              IpSecTransform transform) throws IOException { -        // TODO: call IpSecService +        try { +            mService.applyTunnelModeTransform( +                    tunnel.getResourceId(), direction, transform.getResourceId()); +        } catch (RemoteException e) { +            throw e.rethrowFromSystemServer(); +        }      }      /**       * Construct an instance of IpSecManager within an application context. diff --git a/core/java/android/net/IpSecTunnelInterfaceResponse.aidl b/core/java/android/net/IpSecTunnelInterfaceResponse.aidl new file mode 100644 index 000000000000..7239221415ce --- /dev/null +++ b/core/java/android/net/IpSecTunnelInterfaceResponse.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018 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 IpSecTunnelInterfaceResponse; diff --git a/core/java/android/net/IpSecTunnelInterfaceResponse.java b/core/java/android/net/IpSecTunnelInterfaceResponse.java new file mode 100644 index 000000000000..c23d831a4435 --- /dev/null +++ b/core/java/android/net/IpSecTunnelInterfaceResponse.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 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 IpSecTunnelInterface resource Id and and corresponding status + * from the IpSecService to an IpSecTunnelInterface object. + * + * @hide + */ +public final class IpSecTunnelInterfaceResponse implements Parcelable { +    private static final String TAG = "IpSecTunnelInterfaceResponse"; + +    public final int resourceId; +    public final String interfaceName; +    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); +        out.writeString(interfaceName); +    } + +    public IpSecTunnelInterfaceResponse(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; +        interfaceName = ""; +    } + +    public IpSecTunnelInterfaceResponse(int inStatus, int inResourceId, String inInterfaceName) { +        status = inStatus; +        resourceId = inResourceId; +        interfaceName = inInterfaceName; +    } + +    private IpSecTunnelInterfaceResponse(Parcel in) { +        status = in.readInt(); +        resourceId = in.readInt(); +        interfaceName = in.readString(); +    } + +    public static final Parcelable.Creator<IpSecTunnelInterfaceResponse> CREATOR = +            new Parcelable.Creator<IpSecTunnelInterfaceResponse>() { +                public IpSecTunnelInterfaceResponse createFromParcel(Parcel in) { +                    return new IpSecTunnelInterfaceResponse(in); +                } + +                public IpSecTunnelInterfaceResponse[] newArray(int size) { +                    return new IpSecTunnelInterfaceResponse[size]; +                } +            }; +} diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 6dbf53850ec7..bff5c10d4e82 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -456,8 +456,8 @@ public class ConnectivityService extends IConnectivityManager.Stub      private LingerMonitor mLingerMonitor;      // sequence number for Networks; keep in sync with system/netd/NetworkController.cpp -    private final static int MIN_NET_ID = 100; // some reserved marks -    private final static int MAX_NET_ID = 65535; +    private static final int MIN_NET_ID = 100; // some reserved marks +    private static final int MAX_NET_ID = 65535 - 0x0400; // Top 1024 bits reserved by IpSecService      private int mNextNetId = MIN_NET_ID;      // sequence number of NetworkRequests diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index a9a81089fcbb..c2adbfaeb740 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -34,7 +34,9 @@ import android.net.IpSecManager;  import android.net.IpSecSpiResponse;  import android.net.IpSecTransform;  import android.net.IpSecTransformResponse; +import android.net.IpSecTunnelInterfaceResponse;  import android.net.IpSecUdpEncapResponse; +import android.net.Network;  import android.net.NetworkUtils;  import android.net.TrafficStats;  import android.net.util.NetdService; @@ -50,6 +52,7 @@ import android.text.TextUtils;  import android.util.Log;  import android.util.Slog;  import android.util.SparseArray; +import android.util.SparseBooleanArray;  import com.android.internal.annotations.GuardedBy;  import com.android.internal.annotations.VisibleForTesting; @@ -62,6 +65,7 @@ import java.net.InetAddress;  import java.net.InetSocketAddress;  import java.net.UnknownHostException;  import java.util.ArrayList; +import java.util.BitSet;  import java.util.List;  import libcore.io.IoUtils; @@ -99,6 +103,7 @@ public class IpSecService extends IIpSecService.Stub {      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 +    static final String TUNNEL_INTERFACE_PREFIX = "ipsec";      /* Binder context for this service */      private final Context mContext; @@ -347,6 +352,7 @@ public class IpSecService extends IIpSecService.Stub {      @VisibleForTesting      static final class UserRecord {          /* Maximum number of each type of resource that a single UID may possess */ +        public static final int MAX_NUM_TUNNEL_INTERFACES = 2;          public static final int MAX_NUM_ENCAP_SOCKETS = 2;          public static final int MAX_NUM_TRANSFORMS = 4;          public static final int MAX_NUM_SPIS = 8; @@ -366,6 +372,8 @@ public class IpSecService extends IIpSecService.Stub {                  new RefcountedResourceArray<>(TransformRecord.class.getSimpleName());          final RefcountedResourceArray<EncapSocketRecord> mEncapSocketRecords =                  new RefcountedResourceArray<>(EncapSocketRecord.class.getSimpleName()); +        final RefcountedResourceArray<TunnelInterfaceRecord> mTunnelInterfaceRecords = +                new RefcountedResourceArray<>(TunnelInterfaceRecord.class.getSimpleName());          /**           * Trackers for quotas for each of the OwnedResource types. @@ -379,6 +387,7 @@ public class IpSecService extends IIpSecService.Stub {          final ResourceTracker mSpiQuotaTracker = new ResourceTracker(MAX_NUM_SPIS);          final ResourceTracker mTransformQuotaTracker = new ResourceTracker(MAX_NUM_TRANSFORMS);          final ResourceTracker mSocketQuotaTracker = new ResourceTracker(MAX_NUM_ENCAP_SOCKETS); +        final ResourceTracker mTunnelQuotaTracker = new ResourceTracker(MAX_NUM_TUNNEL_INTERFACES);          void removeSpiRecord(int resourceId) {              mSpiRecords.remove(resourceId); @@ -388,6 +397,10 @@ public class IpSecService extends IIpSecService.Stub {              mTransformRecords.remove(resourceId);          } +        void removeTunnelInterfaceRecord(int resourceId) { +            mTunnelInterfaceRecords.remove(resourceId); +        } +          void removeEncapSocketRecord(int resourceId) {              mEncapSocketRecords.remove(resourceId);          } @@ -721,6 +734,145 @@ public class IpSecService extends IIpSecService.Stub {          }      } +    // These values have been reserved in ConnectivityService +    @VisibleForTesting static final int TUN_INTF_NETID_START = 0xFC00; + +    @VisibleForTesting static final int TUN_INTF_NETID_RANGE = 0x0400; + +    private final SparseBooleanArray mTunnelNetIds = new SparseBooleanArray(); +    private int mNextTunnelNetIdIndex = 0; + +    /** +     * Reserves a netId within the range of netIds allocated for IPsec tunnel interfaces +     * +     * <p>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. +     * +     * @return an integer key within the netId range, if successful +     * @throws IllegalStateException if unsuccessful (all netId are currently reserved) +     */ +    @VisibleForTesting +    int reserveNetId() { +        synchronized (mTunnelNetIds) { +            for (int i = 0; i < TUN_INTF_NETID_RANGE; i++) { +                int index = mNextTunnelNetIdIndex; +                int netId = index + TUN_INTF_NETID_START; +                if (++mNextTunnelNetIdIndex >= TUN_INTF_NETID_RANGE) mNextTunnelNetIdIndex = 0; +                if (!mTunnelNetIds.get(netId)) { +                    mTunnelNetIds.put(netId, true); +                    return netId; +                } +            } +        } +        throw new IllegalStateException("No free netIds to allocate"); +    } + +    @VisibleForTesting +    void releaseNetId(int netId) { +        synchronized (mTunnelNetIds) { +            mTunnelNetIds.delete(netId); +        } +    } + +    private final class TunnelInterfaceRecord extends OwnedResourceRecord { +        private final String mInterfaceName; +        private final Network mUnderlyingNetwork; + +        // outer addresses +        private final String mLocalAddress; +        private final String mRemoteAddress; + +        private final int mIkey; +        private final int mOkey; + +        TunnelInterfaceRecord( +                int resourceId, +                String interfaceName, +                Network underlyingNetwork, +                String localAddr, +                String remoteAddr, +                int ikey, +                int okey) { +            super(resourceId); + +            mInterfaceName = interfaceName; +            mUnderlyingNetwork = underlyingNetwork; +            mLocalAddress = localAddr; +            mRemoteAddress = remoteAddr; +            mIkey = ikey; +            mOkey = okey; +        } + +        /** always guarded by IpSecService#this */ +        @Override +        public void freeUnderlyingResources() { +            // TODO: Add calls to netd +            //       Teardown VTI +            //       Delete global policies + +            getResourceTracker().give(); +            releaseNetId(mIkey); +            releaseNetId(mOkey); +        } + +        public String getInterfaceName() { +            return mInterfaceName; +        } + +        public Network getUnderlyingNetwork() { +            return mUnderlyingNetwork; +        } + +        /** Returns the local, outer address for the tunnelInterface */ +        public String getLocalAddress() { +            return mLocalAddress; +        } + +        /** Returns the remote, outer address for the tunnelInterface */ +        public String getRemoteAddress() { +            return mRemoteAddress; +        } + +        public int getIkey() { +            return mIkey; +        } + +        public int getOkey() { +            return mOkey; +        } + +        @Override +        protected ResourceTracker getResourceTracker() { +            return getUserRecord().mTunnelQuotaTracker; +        } + +        @Override +        public void invalidate() { +            getUserRecord().removeTunnelInterfaceRecord(mResourceId); +        } + +        @Override +        public String toString() { +            return new StringBuilder() +                    .append("{super=") +                    .append(super.toString()) +                    .append(", mInterfaceName=") +                    .append(mInterfaceName) +                    .append(", mUnderlyingNetwork=") +                    .append(mUnderlyingNetwork) +                    .append(", mLocalAddress=") +                    .append(mLocalAddress) +                    .append(", mRemoteAddress=") +                    .append(mRemoteAddress) +                    .append(", mIkey=") +                    .append(mIkey) +                    .append(", mOkey=") +                    .append(mOkey) +                    .append("}") +                    .toString(); +        } +    } +      /**       * Tracks a UDP encap socket, and manages cleanup paths       * @@ -1051,6 +1203,97 @@ public class IpSecService extends IIpSecService.Stub {          releaseResource(userRecord.mEncapSocketRecords, resourceId);      } +    /** +     * Create a tunnel interface for use in IPSec tunnel mode. The system server will cache the +     * tunnel interface and a record of its owner so that it can and must be freed when no longer +     * needed. +     */ +    @Override +    public synchronized IpSecTunnelInterfaceResponse createTunnelInterface( +            String localAddr, String remoteAddr, Network underlyingNetwork, IBinder binder) { +        checkNotNull(binder, "Null Binder passed to createTunnelInterface"); +        checkNotNull(underlyingNetwork, "No underlying network was specified"); +        checkInetAddress(localAddr); +        checkInetAddress(remoteAddr); + +        // TODO: Check that underlying network exists, and IP addresses not assigned to a different +        //       network (b/72316676). + +        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); +        if (!userRecord.mTunnelQuotaTracker.isAvailable()) { +            return new IpSecTunnelInterfaceResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); +        } + +        final int resourceId = mNextResourceId++; +        final int ikey = reserveNetId(); +        final int okey = reserveNetId(); +        String intfName = String.format("%s%d", TUNNEL_INTERFACE_PREFIX, resourceId); + +        // TODO: Add calls to netd: +        //       Create VTI +        //       Add inbound/outbound global policies +        //              (use reqid = 0) + +        userRecord.mTunnelInterfaceRecords.put( +                resourceId, +                new RefcountedResource<TunnelInterfaceRecord>( +                        new TunnelInterfaceRecord( +                                resourceId, +                                intfName, +                                underlyingNetwork, +                                localAddr, +                                remoteAddr, +                                ikey, +                                okey), +                        binder)); +        return new IpSecTunnelInterfaceResponse(IpSecManager.Status.OK, resourceId, intfName); +    } + +    /** +     * Adds a new local address to the tunnel interface. This allows packets to be sent and received +     * from multiple local IP addresses over the same tunnel. +     */ +    @Override +    public synchronized void addAddressToTunnelInterface(int tunnelResourceId, String localAddr) { +        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); + +        // Get tunnelInterface record; if no such interface is found, will throw +        // IllegalArgumentException +        TunnelInterfaceRecord tunnelInterfaceInfo = +                userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId); + +        // TODO: Add calls to netd: +        //       Add address to TunnelInterface +    } + +    /** +     * Remove a new local address from the tunnel interface. After removal, the address will no +     * longer be available to send from, or receive on. +     */ +    @Override +    public synchronized void removeAddressFromTunnelInterface( +            int tunnelResourceId, String localAddr) { +        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); + +        // Get tunnelInterface record; if no such interface is found, will throw +        // IllegalArgumentException +        TunnelInterfaceRecord tunnelInterfaceInfo = +                userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId); + +        // TODO: Add calls to netd: +        //       Remove address from TunnelInterface +    } + +    /** +     * Delete a TunnelInterface that has been been allocated by and registered with the system +     * server +     */ +    @Override +    public synchronized void deleteTunnelInterface(int resourceId) throws RemoteException { +        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); +        releaseResource(userRecord.mTunnelInterfaceRecords, resourceId); +    } +      @VisibleForTesting      void validateAlgorithms(IpSecConfig config) throws IllegalArgumentException {          IpSecAlgorithm auth = config.getAuthentication(); @@ -1249,7 +1492,12 @@ public class IpSecService extends IIpSecService.Stub {              throw new SecurityException("Only the owner of an IpSec Transform may apply it!");          } +        // Get config and check that to-be-applied transform has the correct mode          IpSecConfig c = info.getConfig(); +        Preconditions.checkArgument( +                c.getMode() == IpSecTransform.MODE_TRANSPORT, +                "Transform mode was not Transport mode; cannot be applied to a socket"); +          try {              mSrvConfig                      .getNetdInstance() @@ -1287,6 +1535,42 @@ public class IpSecService extends IIpSecService.Stub {          }      } +    /** +     * Apply an active tunnel mode transform to a TunnelInterface, which will apply the IPsec +     * security association as a correspondent policy to the provided interface +     */ +    @Override +    public synchronized void applyTunnelModeTransform( +            int tunnelResourceId, int direction, int transformResourceId) throws RemoteException { +        checkDirection(direction); + +        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); + +        // Get transform record; if no transform is found, will throw IllegalArgumentException +        TransformRecord transformInfo = +                userRecord.mTransformRecords.getResourceOrThrow(transformResourceId); + +        // Get tunnelInterface record; if no such interface is found, will throw +        // IllegalArgumentException +        TunnelInterfaceRecord tunnelInterfaceInfo = +                userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId); + +        // Get config and check that to-be-applied transform has the correct mode +        IpSecConfig c = transformInfo.getConfig(); +        Preconditions.checkArgument( +                c.getMode() == IpSecTransform.MODE_TUNNEL, +                "Transform mode was not Tunnel mode; cannot be applied to a tunnel interface"); + +        int mark = +                (direction == IpSecManager.DIRECTION_IN) +                        ? tunnelInterfaceInfo.getIkey() +                        : tunnelInterfaceInfo.getOkey(); + +        // TODO: Add calls to netd: +        //       Update SA with tunnel mark (ikey or okey based on direction) +        //       If outbound, add SPI to policy +    } +      @Override      protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {          mContext.enforceCallingOrSelfPermission(DUMP, TAG); diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java index a375b600ca60..2c94a601fbf6 100644 --- a/tests/net/java/com/android/server/IpSecServiceTest.java +++ b/tests/net/java/com/android/server/IpSecServiceTest.java @@ -635,4 +635,25 @@ public class IpSecServiceTest {          verify(mMockNetd).ipSecSetEncapSocketOwner(argThat(fdMatcher), eq(Os.getuid()));          mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);      } + +    @Test +    public void testReserveNetId() { +        int start = mIpSecService.TUN_INTF_NETID_START; +        for (int i = 0; i < mIpSecService.TUN_INTF_NETID_RANGE; i++) { +            assertEquals(start + i, mIpSecService.reserveNetId()); +        } + +        // Check that resource exhaustion triggers an exception +        try { +            mIpSecService.reserveNetId(); +            fail("Did not throw error for all netIds reserved"); +        } catch (IllegalStateException expected) { +        } + +        // Now release one and try again +        int releasedNetId = +                mIpSecService.TUN_INTF_NETID_START + mIpSecService.TUN_INTF_NETID_RANGE / 2; +        mIpSecService.releaseNetId(releasedNetId); +        assertEquals(releasedNetId, mIpSecService.reserveNetId()); +    }  }  |