Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2ee367ec authored by junyulai's avatar junyulai Committed by Chalard Jean
Browse files

Support customization of supported keepalive count per transport

This change specifies the required minimum supported keepalives
in SDK, and allows OEMs to customize supported keepalive count
per network through resource overlay.

Bug: 129371366
Test: 1. m -j doc-comment-check-docs
      2. atest FrameworksNetTests

Clean cherry-pick of aosp/946359

Change-Id: I06840834d0ee8121358bf4829fe47ecf9964d395
Merged-In: I0218f3674628c13ead63fc9a873895ba7f113033
Merged-In: Ia667386c1a8949839871a6949d79552d9c8b88f0
parent ac3b5006
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -43,6 +43,10 @@ import java.util.concurrent.Executor;
 * To stop an existing keepalive, call {@link SocketKeepalive#stop}. The system will call
 * {@link SocketKeepalive.Callback#onStopped} if the operation was successful or
 * {@link SocketKeepalive.Callback#onError} if an error occurred.
 *
 * The device SHOULD support keepalive offload. If it does not, it MUST reply with
 * {@link SocketKeepalive.Callback#onError} with {@code ERROR_UNSUPPORTED} to any keepalive offload
 * request. If it does, it MUST support at least 3 concurrent keepalive slots per transport.
 */
public abstract class SocketKeepalive implements AutoCloseable {
    static final String TAG = "SocketKeepalive";
+121 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.util;

import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.net.NetworkCapabilities;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;

import com.android.internal.R;

/**
 * Collection of utilities for socket keepalive offload.
 *
 * @hide
 */
public final class KeepaliveUtils {

    public static final String TAG = "KeepaliveUtils";

    // Minimum supported keepalive count per transport if the network supports keepalive.
    public static final int MIN_SUPPORTED_KEEPALIVE_COUNT = 3;

    public static class KeepaliveDeviceConfigurationException extends AndroidRuntimeException {
        public KeepaliveDeviceConfigurationException(final String msg) {
            super(msg);
        }
    }

    /**
     * Read supported keepalive count for each transport type from overlay resource. This should be
     * used to create a local variable store of resource customization, and use it as the input for
     * {@link getSupportedKeepalivesForNetworkCapabilities}.
     *
     * @param context The context to read resource from.
     * @return An array of supported keepalive count for each transport type.
     */
    @NonNull
    public static int[] getSupportedKeepalives(@NonNull Context context) {
        String[] res = null;
        try {
            res = context.getResources().getStringArray(
                    R.array.config_networkSupportedKeepaliveCount);
        } catch (Resources.NotFoundException unused) {
        }
        if (res == null) throw new KeepaliveDeviceConfigurationException("invalid resource");

        final int[] ret = new int[NetworkCapabilities.MAX_TRANSPORT + 1];
        for (final String row : res) {
            if (TextUtils.isEmpty(row)) {
                throw new KeepaliveDeviceConfigurationException("Empty string");
            }
            final String[] arr = row.split(",");
            if (arr.length != 2) {
                throw new KeepaliveDeviceConfigurationException("Invalid parameter length");
            }

            int transport;
            int supported;
            try {
                transport = Integer.parseInt(arr[0]);
                supported = Integer.parseInt(arr[1]);
            } catch (NumberFormatException e) {
                throw new KeepaliveDeviceConfigurationException("Invalid number format");
            }

            if (!NetworkCapabilities.isValidTransport(transport)) {
                throw new KeepaliveDeviceConfigurationException("Invalid transport " + transport);
            }

            // Customized values should be either 0 to indicate the network doesn't support
            // keepalive offload, or a positive value that is at least
            // MIN_SUPPORTED_KEEPALIVE_COUNT if supported.
            if (supported != 0 && supported < MIN_SUPPORTED_KEEPALIVE_COUNT) {
                throw new KeepaliveDeviceConfigurationException(
                        "Invalid supported count " + supported + " for "
                                + NetworkCapabilities.transportNameOf(transport));
            }
            ret[transport] = supported;
        }
        return ret;
    }

    /**
     * Get supported keepalive count for the given {@link NetworkCapabilities}.
     *
     * @param supportedKeepalives An array of supported keepalive count for each transport type.
     * @param nc The {@link NetworkCapabilities} of the network the socket keepalive is on.
     *
     * @return Supported keepalive count for the given {@link NetworkCapabilities}.
     */
    public static int getSupportedKeepalivesForNetworkCapabilities(
            @NonNull int[] supportedKeepalives, @NonNull NetworkCapabilities nc) {
        final int[] transports = nc.getTransportTypes();
        if (transports.length == 0) return 0;
        int supportedCount = supportedKeepalives[transports[0]];
        // Iterate through transports and return minimum supported value.
        for (final int transport : transports) {
            if (supportedCount > supportedKeepalives[transport]) {
                supportedCount = supportedKeepalives[transport];
            }
        }
        return supportedCount;
    }
}
+13 −0
Original line number Diff line number Diff line
@@ -358,6 +358,19 @@
         default value of that setting. -->
    <integer translatable="false" name="config_networkDefaultDailyMultipathQuotaBytes">2500000</integer>

    <!-- Default supported concurrent socket keepalive slots per transport type, used by
         ConnectivityManager.createSocketKeepalive() for calculating the number of keepalive
         offload slots that should be reserved for privileged access. This string array should be
         overridden by the device to present the capability of creating socket keepalives. -->
    <!-- An Array of "[NetworkCapabilities.TRANSPORT_*],[supported keepalives] -->
    <string-array translatable="false" name="config_networkSupportedKeepaliveCount">
        <item>0,3</item>
        <item>1,3</item>
    </string-array>

    <!-- Reserved privileged keepalive slots per transport. -->
    <integer translatable="false" name="config_reservedPrivilegedKeepaliveSlots">2</integer>

    <!-- List of regexpressions describing the interface (if any) that represent tetherable
         USB interfaces.  If the device doesn't want to support tethering over USB this should
         be empty.  An example would be "usb.*" -->
+2 −0
Original line number Diff line number Diff line
@@ -2026,6 +2026,8 @@
  <java-symbol type="array" name="config_apfEthTypeBlackList" />
  <java-symbol type="integer" name="config_networkDefaultDailyMultipathQuotaBytes" />
  <java-symbol type="integer" name="config_networkMeteredMultipathPreference" />
  <java-symbol type="array" name="config_networkSupportedKeepaliveCount" />
  <java-symbol type="integer" name="config_reservedPrivilegedKeepaliveSlots" />
  <java-symbol type="integer" name="config_notificationsBatteryFullARGB" />
  <java-symbol type="integer" name="config_notificationsBatteryLedOff" />
  <java-symbol type="integer" name="config_notificationsBatteryLedOn" />
+45 −13
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import static android.net.SocketKeepalive.ERROR_INVALID_INTERVAL;
import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
import static android.net.SocketKeepalive.ERROR_INVALID_NETWORK;
import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
import static android.net.SocketKeepalive.ERROR_UNSUPPORTED;
import static android.net.SocketKeepalive.MAX_INTERVAL_SEC;
import static android.net.SocketKeepalive.MIN_INTERVAL_SEC;
import static android.net.SocketKeepalive.NO_KEEPALIVE;
@@ -46,6 +47,7 @@ import android.net.SocketKeepalive.InvalidPacketException;
import android.net.SocketKeepalive.InvalidSocketException;
import android.net.TcpKeepalivePacketData;
import android.net.util.IpUtils;
import android.net.util.KeepaliveUtils;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -57,6 +59,7 @@ import android.system.Os;
import android.util.Log;
import android.util.Pair;

import com.android.internal.R;
import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;

@@ -65,6 +68,7 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

/**
@@ -90,10 +94,23 @@ public class KeepaliveTracker {
    @NonNull
    private final Context mContext;

    // Supported keepalive count for each transport type, can be configured through
    // config_networkSupportedKeepaliveCount. For better error handling, use
    // {@link getSupportedKeepalivesForNetworkCapabilities} instead of direct access.
    @NonNull
    private final int[] mSupportedKeepalives;

    // Reserved privileged keepalive slots per transport. Caller's permission will be enforced if
    // the number of remaining keepalive slots is less than or equal to the threshold.
    private final int mReservedPrivilegedSlots;

    public KeepaliveTracker(Context context, Handler handler) {
        mConnectivityServiceHandler = handler;
        mTcpController = new TcpKeepaliveController(handler);
        mContext = context;
        mSupportedKeepalives = KeepaliveUtils.getSupportedKeepalives(mContext);
        mReservedPrivilegedSlots = mContext.getResources().getInteger(
                R.integer.config_reservedPrivilegedKeepaliveSlots);
    }

    /**
@@ -115,11 +132,6 @@ public class KeepaliveTracker {
        public static final int TYPE_NATT = 1;
        public static final int TYPE_TCP = 2;

        // Max allowed unprivileged keepalive slots per network. Caller's permission will be
        // enforced if number of existing keepalives reach this limit.
        // TODO: consider making this limit configurable via resources.
        private static final int MAX_UNPRIVILEGED_SLOTS = 3;

        // Keepalive slot. A small integer that identifies this keepalive among the ones handled
        // by this network.
        private int mSlot = NO_KEEPALIVE;
@@ -246,24 +258,42 @@ public class KeepaliveTracker {

        private int checkPermission() {
            final HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(mNai);
            int unprivilegedCount = 0;
            if (networkKeepalives == null) {
                return ERROR_INVALID_NETWORK;
            }
            for (KeepaliveInfo ki : networkKeepalives.values()) {
                if (!ki.mPrivileged) {
                    unprivilegedCount++;

            if (mPrivileged) return SUCCESS;

            final int supported = KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(
                    mSupportedKeepalives, mNai.networkCapabilities);

            int takenUnprivilegedSlots = 0;
            for (final KeepaliveInfo ki : networkKeepalives.values()) {
                if (!ki.mPrivileged) ++takenUnprivilegedSlots;
            }
                if (unprivilegedCount >= MAX_UNPRIVILEGED_SLOTS) {
                    return mPrivileged ? SUCCESS : ERROR_INSUFFICIENT_RESOURCES;
            if (takenUnprivilegedSlots > supported - mReservedPrivilegedSlots) {
                return ERROR_INSUFFICIENT_RESOURCES;
            }

            return SUCCESS;
        }

        private int checkLimit() {
            final HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(mNai);
            if (networkKeepalives == null) {
                return ERROR_INVALID_NETWORK;
            }
            final int supported = KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(
                    mSupportedKeepalives, mNai.networkCapabilities);
            if (supported == 0) return ERROR_UNSUPPORTED;
            if (networkKeepalives.size() > supported) return ERROR_INSUFFICIENT_RESOURCES;
            return SUCCESS;
        }

        private int isValid() {
            synchronized (mNai) {
                int error = checkInterval();
                if (error == SUCCESS) error = checkLimit();
                if (error == SUCCESS) error = checkPermission();
                if (error == SUCCESS) error = checkNetworkConnected();
                if (error == SUCCESS) error = checkSourceAddress();
@@ -642,6 +672,8 @@ public class KeepaliveTracker {
    }

    public void dump(IndentingPrintWriter pw) {
        pw.println("Supported Socket keepalives: " + Arrays.toString(mSupportedKeepalives));
        pw.println("Reserved Privileged keepalives: " + mReservedPrivilegedSlots);
        pw.println("Socket keepalives:");
        pw.increaseIndent();
        for (NetworkAgentInfo nai : mKeepalives.keySet()) {
Loading