Merge "[DK2]Add new SocketKeepalive.start to dynamically control keepalive"
diff --git a/Tethering/AndroidManifest.xml b/Tethering/AndroidManifest.xml
index b832e16..23467e7 100644
--- a/Tethering/AndroidManifest.xml
+++ b/Tethering/AndroidManifest.xml
@@ -43,7 +43,9 @@
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
+ <!-- Sending non-protected broadcast from system uid is not allowed. -->
<protected-broadcast android:name="com.android.server.connectivity.tethering.DISABLE_TETHERING" />
+ <protected-broadcast android:name="com.android.server.connectivity.KeepaliveTracker.TCP_POLLING_ALARM" />
<application
android:process="com.android.networkstack.process"
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index dd3404c..0b03983 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -470,7 +470,9 @@
}
public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+ method public final void start(@IntRange(from=0xa, to=0xe10) int, int);
field public static final int ERROR_NO_SUCH_SLOT = -33; // 0xffffffdf
+ field public static final int FLAG_AUTOMATIC_ON_OFF = 1; // 0x1
field public static final int SUCCESS = 0; // 0x0
}
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index 7b6e769..7db231e 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -188,7 +188,7 @@
void startNattKeepaliveWithFd(in Network network, in ParcelFileDescriptor pfd, int resourceId,
int intervalSeconds, in ISocketKeepaliveCallback cb, String srcAddr,
- String dstAddr);
+ String dstAddr, boolean automaticOnOffKeepalives);
void startTcpKeepalive(in Network network, in ParcelFileDescriptor pfd, int intervalSeconds,
in ISocketKeepaliveCallback cb);
diff --git a/framework/src/android/net/NattSocketKeepalive.java b/framework/src/android/net/NattSocketKeepalive.java
index 56cc923..4d45e70 100644
--- a/framework/src/android/net/NattSocketKeepalive.java
+++ b/framework/src/android/net/NattSocketKeepalive.java
@@ -47,13 +47,39 @@
mResourceId = resourceId;
}
+ /**
+ * Request that keepalive be started with the given {@code intervalSec}.
+ *
+ * When a VPN is running with the network for this keepalive as its underlying network, the
+ * system can monitor the TCP connections on that VPN to determine whether this keepalive is
+ * necessary. To enable this behavior, pass {@link SocketKeepalive#FLAG_AUTOMATIC_ON_OFF} into
+ * the flags. When this is enabled, the system will disable sending keepalive packets when
+ * there are no TCP connections over the VPN(s) running over this network to save battery, and
+ * restart sending them as soon as any TCP connection is opened over one of the VPN networks.
+ * When no VPN is running on top of this network, this flag has no effect, i.e. the keepalives
+ * are always sent with the specified interval.
+ *
+ * Also {@see SocketKeepalive}.
+ *
+ * @param intervalSec The target interval in seconds between keepalive packet transmissions.
+ * The interval should be between 10 seconds and 3600 seconds. Otherwise,
+ * the supplied {@link Callback} will see a call to
+ * {@link Callback#onError(int)} with {@link #ERROR_INVALID_INTERVAL}.
+ * @param flags Flags to enable/disable available options on this keepalive.
+ * @hide
+ */
@Override
- protected void startImpl(int intervalSec) {
+ protected void startImpl(int intervalSec, int flags) {
+ if (0 != (flags & ~FLAG_AUTOMATIC_ON_OFF)) {
+ throw new IllegalArgumentException("Illegal flag value for "
+ + this.getClass().getSimpleName() + " : " + flags);
+ }
+ final boolean automaticOnOffKeepalives = 0 != (flags & FLAG_AUTOMATIC_ON_OFF);
mExecutor.execute(() -> {
try {
mService.startNattKeepaliveWithFd(mNetwork, mPfd, mResourceId,
- intervalSec, mCallback,
- mSource.getHostAddress(), mDestination.getHostAddress());
+ intervalSec, mCallback, mSource.getHostAddress(),
+ mDestination.getHostAddress(), automaticOnOffKeepalives);
} catch (RemoteException e) {
Log.e(TAG, "Error starting socket keepalive: ", e);
throw e.rethrowFromSystemServer();
diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index 1486619..732bd87 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -483,6 +483,20 @@
*/
public static final int EVENT_UNREGISTER_AFTER_REPLACEMENT = BASE + 29;
+ /**
+ * Sent by AutomaticOnOffKeepaliveTracker periodically (when relevant) to trigger monitor
+ * automatic keepalive request.
+ *
+ * NATT keepalives have an automatic mode where the system only sends keepalive packets when
+ * TCP sockets are open over a VPN. The system will check periodically for presence of
+ * such open sockets, and this message is what triggers the re-evaluation.
+ *
+ * arg1 = hardware slot number of the keepalive
+ * obj = {@link Network} that the keepalive is started on.
+ * @hide
+ */
+ public static final int CMD_MONITOR_AUTOMATIC_KEEPALIVE = BASE + 30;
+
private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
config.legacyTypeName, config.legacySubTypeName);
diff --git a/framework/src/android/net/SocketKeepalive.java b/framework/src/android/net/SocketKeepalive.java
index 57cf5e3..90e5e9b 100644
--- a/framework/src/android/net/SocketKeepalive.java
+++ b/framework/src/android/net/SocketKeepalive.java
@@ -16,6 +16,8 @@
package android.net;
+import static android.annotation.SystemApi.Client.PRIVILEGED_APPS;
+
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -174,6 +176,27 @@
public @interface KeepaliveEvent {}
/**
+ * Whether the system automatically toggles keepalive when no TCP connection is open on the VPN.
+ *
+ * If this flag is present, the system will monitor the VPN(s) running on top of the specified
+ * network for open TCP connections. When no such connections are open, it will turn off the
+ * keepalives to conserve battery power. When there is at least one such connection it will
+ * turn on the keepalives to make sure functionality is preserved.
+ *
+ * This only works with {@link NattSocketKeepalive}.
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_AUTOMATIC_ON_OFF = 1 << 0;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "FLAG_"}, flag = true, value = {
+ FLAG_AUTOMATIC_ON_OFF
+ })
+ public @interface StartFlags {}
+
+ /**
* The minimum interval in seconds between keepalive packet transmissions.
*
* @hide
@@ -294,13 +317,15 @@
}
/**
- * Request that keepalive be started with the given {@code intervalSec}. See
- * {@link SocketKeepalive}. If the remote binder dies, or the binder call throws an exception
- * when invoking start or stop of the {@link SocketKeepalive}, a {@link RemoteException} will be
- * thrown into the {@code executor}. This is typically not important to catch because the remote
- * party is the system, so if it is not in shape to communicate through binder the system is
- * probably going down anyway. If the caller cares regardless, it can use a custom
- * {@link Executor} to catch the {@link RemoteException}.
+ * Request that keepalive be started with the given {@code intervalSec}.
+ *
+ * See {@link SocketKeepalive}. If the remote binder dies, or the binder call throws an
+ * exception when invoking start or stop of the {@link SocketKeepalive}, a
+ * {@link RuntimeException} caused by a {@link RemoteException} will be thrown into the
+ * {@link Executor}. This is typically not important to catch because the remote party is
+ * the system, so if it is not in shape to communicate through binder the system is going
+ * down anyway. If the caller still cares, it can use a custom {@link Executor} to catch the
+ * {@link RuntimeException}.
*
* @param intervalSec The target interval in seconds between keepalive packet transmissions.
* The interval should be between 10 seconds and 3600 seconds, otherwise
@@ -308,11 +333,35 @@
*/
public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC)
int intervalSec) {
- startImpl(intervalSec);
+ startImpl(intervalSec, 0 /* flags */);
+ }
+
+ /**
+ * Request that keepalive be started with the given {@code intervalSec}.
+ *
+ * See {@link SocketKeepalive}. If the remote binder dies, or the binder call throws an
+ * exception when invoking start or stop of the {@link SocketKeepalive}, a
+ * {@link RuntimeException} caused by a {@link RemoteException} will be thrown into the
+ * {@link Executor}. This is typically not important to catch because the remote party is
+ * the system, so if it is not in shape to communicate through binder the system is going
+ * down anyway. If the caller still cares, it can use a custom {@link Executor} to catch the
+ * {@link RuntimeException}.
+ *
+ * @param intervalSec The target interval in seconds between keepalive packet transmissions.
+ * The interval should be between 10 seconds and 3600 seconds. Otherwise,
+ * the supplied {@link Callback} will see a call to
+ * {@link Callback#onError(int)} with {@link #ERROR_INVALID_INTERVAL}.
+ * @param flags Flags to enable/disable available options on this keepalive.
+ * @hide
+ */
+ @SystemApi(client = PRIVILEGED_APPS)
+ public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC)
+ int intervalSec, @StartFlags int flags) {
+ startImpl(intervalSec, flags);
}
/** @hide */
- protected abstract void startImpl(int intervalSec);
+ protected abstract void startImpl(int intervalSec, @StartFlags int flags);
/**
* Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped}
diff --git a/framework/src/android/net/TcpSocketKeepalive.java b/framework/src/android/net/TcpSocketKeepalive.java
index 7131784..51d805e 100644
--- a/framework/src/android/net/TcpSocketKeepalive.java
+++ b/framework/src/android/net/TcpSocketKeepalive.java
@@ -50,7 +50,11 @@
* acknowledgement.
*/
@Override
- protected void startImpl(int intervalSec) {
+ protected void startImpl(int intervalSec, int flags) {
+ if (0 != flags) {
+ throw new IllegalArgumentException("Illegal flag value for "
+ + this.getClass().getSimpleName() + " : " + flags);
+ }
mExecutor.execute(() -> {
try {
mService.startTcpKeepalive(mNetwork, mPfd, intervalSec, mCallback);
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 87ac0a8..b9d2760 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -101,7 +101,6 @@
import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission;
import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr;
-import static com.android.server.connectivity.KeepaliveTracker.PERMISSION;
import static java.util.Map.Entry;
@@ -278,6 +277,7 @@
import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
import com.android.server.connectivity.DscpPolicyTracker;
import com.android.server.connectivity.FullScore;
+import com.android.server.connectivity.KeepaliveTracker;
import com.android.server.connectivity.LingerMonitor;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.MultinetworkPolicyTracker;
@@ -2999,7 +2999,7 @@
}
private void enforceKeepalivePermission() {
- mContext.enforceCallingOrSelfPermission(PERMISSION, "ConnectivityService");
+ mContext.enforceCallingOrSelfPermission(KeepaliveTracker.PERMISSION, "ConnectivityService");
}
private boolean checkLocalMacAddressPermission(int pid, int uid) {
@@ -5545,6 +5545,33 @@
mKeepaliveTracker.handleStartKeepalive(msg);
break;
}
+ case NetworkAgent.CMD_MONITOR_AUTOMATIC_KEEPALIVE: {
+ final Network network = (Network) msg.obj;
+ final int slot = msg.arg1;
+
+ boolean networkFound = false;
+ final ArrayList<NetworkAgentInfo> vpnsRunningOnThisNetwork = new ArrayList<>();
+ for (NetworkAgentInfo n : mNetworkAgentInfos) {
+ if (n.network.equals(network)) networkFound = true;
+ if (n.isVPN() && n.everConnected() && hasUnderlyingNetwork(n, network)) {
+ vpnsRunningOnThisNetwork.add(n);
+ }
+ }
+
+ // If the network no longer exists, then the keepalive should have been
+ // cleaned up already. There is no point trying to resume keepalives.
+ if (!networkFound) return;
+
+ if (!vpnsRunningOnThisNetwork.isEmpty()) {
+ mKeepaliveTracker.handleMonitorAutomaticKeepalive(network, slot,
+ // TODO: check all the VPNs running on top of this network
+ vpnsRunningOnThisNetwork.get(0).network.netId);
+ } else {
+ // If no VPN, then make sure the keepalive is running.
+ mKeepaliveTracker.handleMaybeResumeKeepalive(network, slot);
+ }
+ break;
+ }
// Sent by KeepaliveTracker to process an app request on the state machine thread.
case NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE: {
NetworkAgentInfo nai = getNetworkAgentInfoForNetwork((Network) msg.obj);
@@ -9789,20 +9816,23 @@
enforceKeepalivePermission();
mKeepaliveTracker.startNattKeepalive(
getNetworkAgentInfoForNetwork(network), null /* fd */,
- intervalSeconds, cb,
- srcAddr, srcPort, dstAddr, NattSocketKeepalive.NATT_PORT);
+ intervalSeconds, cb, srcAddr, srcPort, dstAddr, NattSocketKeepalive.NATT_PORT,
+ // Keep behavior of the deprecated method as it is. Set automaticOnOffKeepalives to
+ // false because there is no way and no plan to configure automaticOnOffKeepalives
+ // in this deprecated method.
+ false /* automaticOnOffKeepalives */);
}
@Override
public void startNattKeepaliveWithFd(Network network, ParcelFileDescriptor pfd, int resourceId,
int intervalSeconds, ISocketKeepaliveCallback cb, String srcAddr,
- String dstAddr) {
+ String dstAddr, boolean automaticOnOffKeepalives) {
try {
final FileDescriptor fd = pfd.getFileDescriptor();
mKeepaliveTracker.startNattKeepalive(
getNetworkAgentInfoForNetwork(network), fd, resourceId,
intervalSeconds, cb,
- srcAddr, dstAddr, NattSocketKeepalive.NATT_PORT);
+ srcAddr, dstAddr, NattSocketKeepalive.NATT_PORT, automaticOnOffKeepalives);
} finally {
// FileDescriptors coming from AIDL calls must be manually closed to prevent leaks.
// startNattKeepalive calls Os.dup(fd) before returning, so we can close immediately.
diff --git a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
index 85ec5e3..5d1d378 100644
--- a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
@@ -16,6 +16,9 @@
package com.android.server.connectivity;
+import static android.net.NetworkAgent.CMD_START_SOCKET_KEEPALIVE;
+import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
+import static android.net.SocketKeepalive.SUCCESS;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
import static android.system.OsConstants.SOL_SOCKET;
@@ -26,16 +29,28 @@
import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
import static com.android.net.module.util.netlink.NetlinkUtils.IO_TIMEOUT_MS;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.net.INetd;
import android.net.ISocketKeepaliveCallback;
import android.net.MarkMaskParcel;
+import android.net.Network;
+import android.net.NetworkAgent;
+import android.net.SocketKeepalive;
+import android.net.SocketKeepalive.InvalidSocketException;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructTimeval;
@@ -44,6 +59,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.HexDump;
import com.android.net.module.util.SocketUtils;
import com.android.net.module.util.netlink.InetDiagMessage;
@@ -52,23 +68,57 @@
import java.io.FileDescriptor;
import java.io.InterruptedIOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.net.SocketException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
import java.util.Objects;
/**
* Manages automatic on/off socket keepalive requests.
*
* Provides methods to stop and start automatic keepalive requests, and keeps track of keepalives
- * across all networks. For non-automatic on/off keepalive request, this class bypass the requests
- * and send to KeepaliveTrakcer. This class is tightly coupled to ConnectivityService. It is not
+ * across all networks. For non-automatic on/off keepalive request, this class just forwards the
+ * requests to KeepaliveTracker. This class is tightly coupled to ConnectivityService. It is not
* thread-safe and its handle* methods must be called only from the ConnectivityService handler
* thread.
*/
public class AutomaticOnOffKeepaliveTracker {
private static final String TAG = "AutomaticOnOffKeepaliveTracker";
private static final int[] ADDRESS_FAMILIES = new int[] {AF_INET6, AF_INET};
+ private static final String ACTION_TCP_POLLING_ALARM =
+ "com.android.server.connectivity.KeepaliveTracker.TCP_POLLING_ALARM";
+ private static final String EXTRA_NETWORK = "network_id";
+ private static final String EXTRA_SLOT = "slot";
+ private static final long DEFAULT_TCP_POLLING_INTERVAL_MS = 120_000L;
+ /**
+ * States for {@code #AutomaticOnOffKeepalive}.
+ *
+ * A new AutomaticOnOffKeepalive starts with STATE_ENABLED. The system will monitor
+ * the TCP sockets on VPN networks running on top of the specified network, and turn off
+ * keepalive if there is no TCP socket any of the VPN networks. Conversely, it will turn
+ * keepalive back on if any TCP socket is open on any of the VPN networks.
+ *
+ * When there is no TCP socket on any of the VPN networks, the state becomes STATE_SUSPENDED.
+ * The {@link KeepaliveTracker.KeepaliveInfo} object is kept to remember the parameters so it
+ * is possible to resume keepalive later with the same parameters.
+ *
+ * When the system detects some TCP socket is open on one of the VPNs while in STATE_SUSPENDED,
+ * this AutomaticOnOffKeepalive goes to STATE_ENABLED again.
+ *
+ * When finishing keepalive, this object is deleted.
+ */
+ private static final int STATE_ENABLED = 0;
+ private static final int STATE_SUSPENDED = 1;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "STATE_" }, value = {
+ STATE_ENABLED,
+ STATE_SUSPENDED
+ })
+ private @interface AutomaticOnOffState {}
@NonNull
private final Handler mConnectivityServiceHandler;
@@ -76,6 +126,8 @@
private final KeepaliveTracker mKeepaliveTracker;
@NonNull
private final Context mContext;
+ @NonNull
+ private final AlarmManager mAlarmManager;
/**
* The {@code inetDiagReqV2} messages for different IP family.
@@ -88,8 +140,73 @@
private final SparseArray<byte[]> mSockDiagMsg = new SparseArray<>();
private final Dependencies mDependencies;
private final INetd mNetd;
+ /**
+ * Keeps track of automatic on/off keepalive requests.
+ * This should be only updated in ConnectivityService handler thread.
+ */
+ private final ArrayList<AutomaticOnOffKeepalive> mAutomaticOnOffKeepalives = new ArrayList<>();
- public AutomaticOnOffKeepaliveTracker(Context context, Handler handler) {
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_TCP_POLLING_ALARM.equals(intent.getAction())) {
+ Log.d(TAG, "Received TCP polling intent");
+ final Network network = intent.getParcelableExtra(EXTRA_NETWORK);
+ final int slot = intent.getIntExtra(EXTRA_SLOT, -1);
+ mConnectivityServiceHandler.obtainMessage(
+ NetworkAgent.CMD_MONITOR_AUTOMATIC_KEEPALIVE,
+ slot, 0 , network).sendToTarget();
+ }
+ }
+ };
+
+ private static class AutomaticOnOffKeepalive {
+ @NonNull
+ private final KeepaliveTracker.KeepaliveInfo mKi;
+ @NonNull
+ private final FileDescriptor mFd;
+ @NonNull
+ private final PendingIntent mTcpPollingAlarm;
+ private final int mSlot;
+ @AutomaticOnOffState
+ private int mAutomaticOnOffState = STATE_ENABLED;
+
+ AutomaticOnOffKeepalive(@NonNull KeepaliveTracker.KeepaliveInfo ki,
+ @NonNull Context context) throws InvalidSocketException {
+ this.mKi = Objects.requireNonNull(ki);
+ // A null fd is acceptable in KeepaliveInfo for backward compatibility of
+ // PacketKeepalive API, but it should not happen here because legacy API cannot setup
+ // automatic keepalive.
+ Objects.requireNonNull(ki.mFd);
+
+ // Get the slot from keepalive because the slot information may be missing when the
+ // keepalive is stopped.
+ this.mSlot = ki.getSlot();
+ try {
+ this.mFd = Os.dup(ki.mFd);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot dup fd: ", e);
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
+ }
+ mTcpPollingAlarm = createTcpPollingAlarmIntent(
+ context, ki.getNai().network(), ki.getSlot());
+ }
+
+ public boolean match(Network network, int slot) {
+ return this.mKi.getNai().network().equals(network) && this.mSlot == slot;
+ }
+
+ private static PendingIntent createTcpPollingAlarmIntent(@NonNull Context context,
+ @NonNull Network network, int slot) {
+ final Intent intent = new Intent(ACTION_TCP_POLLING_ALARM);
+ intent.putExtra(EXTRA_NETWORK, network);
+ intent.putExtra(EXTRA_SLOT, slot);
+ return PendingIntent.getBroadcast(
+ context, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE);
+ }
+ }
+
+ public AutomaticOnOffKeepaliveTracker(@NonNull Context context, @NonNull Handler handler) {
this(context, handler, new Dependencies(context));
}
@@ -97,15 +214,111 @@
public AutomaticOnOffKeepaliveTracker(@NonNull Context context, @NonNull Handler handler,
@NonNull Dependencies dependencies) {
mContext = Objects.requireNonNull(context);
- mDependencies = dependencies;
- this.mConnectivityServiceHandler = Objects.requireNonNull(handler);
+ mDependencies = Objects.requireNonNull(dependencies);
+ mConnectivityServiceHandler = Objects.requireNonNull(handler);
mNetd = mDependencies.getNetd();
mKeepaliveTracker = mDependencies.newKeepaliveTracker(
mContext, mConnectivityServiceHandler);
+
+ if (SdkLevel.isAtLeastU()) {
+ mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_TCP_POLLING_ALARM),
+ null, handler);
+ }
+ mAlarmManager = mContext.getSystemService(AlarmManager.class);
+ }
+
+ private void startTcpPollingAlarm(@NonNull PendingIntent alarm) {
+ final long triggerAtMillis =
+ SystemClock.elapsedRealtime() + DEFAULT_TCP_POLLING_INTERVAL_MS;
+ // Setup a non-wake up alarm.
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME, triggerAtMillis, alarm);
+ }
+
+ /**
+ * Determine if any state transition is needed for the specific automatic keepalive.
+ */
+ public void handleMonitorAutomaticKeepalive(@NonNull Network network, int slot, int vpnNetId) {
+ final AutomaticOnOffKeepalive autoKi = findAutomaticOnOffKeepalive(network, slot);
+ // This may happen if the keepalive is removed by the app, and the alarm is fired at the
+ // same time.
+ if (autoKi == null) return;
+
+ handleMonitorTcpConnections(autoKi, vpnNetId);
+ }
+
+ /**
+ * Determine if disable or re-enable keepalive is needed or not based on TCP sockets status.
+ */
+ private void handleMonitorTcpConnections(@NonNull AutomaticOnOffKeepalive ki, int vpnNetId) {
+ if (!isAnyTcpSocketConnected(vpnNetId)) {
+ // No TCP socket exists. Stop keepalive if ENABLED, and remain SUSPENDED if currently
+ // SUSPENDED.
+ if (ki.mAutomaticOnOffState == STATE_ENABLED) {
+ ki.mAutomaticOnOffState = STATE_SUSPENDED;
+ handleSuspendKeepalive(ki.mKi.mNai, ki.mSlot, SUCCESS);
+ }
+ } else {
+ handleMaybeResumeKeepalive(ki);
+ }
+ // TODO: listen to socket status instead of periodically check.
+ startTcpPollingAlarm(ki.mTcpPollingAlarm);
+ }
+
+ /**
+ * Resume keepalive for this slot on this network, if it wasn't already resumed.
+ */
+ public void handleMaybeResumeKeepalive(@NonNull final Network network, final int slot) {
+ final AutomaticOnOffKeepalive autoKi = findAutomaticOnOffKeepalive(network, slot);
+ // This may happen if the keepalive is removed by the app, and the alarm is fired at
+ // the same time.
+ if (autoKi == null) return;
+ handleMaybeResumeKeepalive(autoKi);
+ }
+
+ private void handleMaybeResumeKeepalive(@NonNull AutomaticOnOffKeepalive autoKi) {
+ if (autoKi.mAutomaticOnOffState == STATE_ENABLED) return;
+ KeepaliveTracker.KeepaliveInfo newKi;
+ try {
+ // Get fd from AutomaticOnOffKeepalive since the fd in the original
+ // KeepaliveInfo should be closed.
+ newKi = autoKi.mKi.withFd(autoKi.mFd);
+ } catch (InvalidSocketException | IllegalArgumentException | SecurityException e) {
+ Log.e(TAG, "Fail to construct keepalive", e);
+ mKeepaliveTracker.notifyErrorCallback(autoKi.mKi.mCallback, ERROR_INVALID_SOCKET);
+ return;
+ }
+ autoKi.mAutomaticOnOffState = STATE_ENABLED;
+ handleResumeKeepalive(mConnectivityServiceHandler.obtainMessage(
+ NetworkAgent.CMD_START_SOCKET_KEEPALIVE,
+ autoKi.mAutomaticOnOffState, 0, newKi));
+ }
+
+ private int findAutomaticOnOffKeepaliveIndex(@NonNull Network network, int slot) {
+ ensureRunningOnHandlerThread();
+
+ int index = 0;
+ for (AutomaticOnOffKeepalive ki : mAutomaticOnOffKeepalives) {
+ if (ki.match(network, slot)) {
+ return index;
+ }
+ index++;
+ }
+ return -1;
+ }
+
+ @Nullable
+ private AutomaticOnOffKeepalive findAutomaticOnOffKeepalive(@NonNull Network network,
+ int slot) {
+ ensureRunningOnHandlerThread();
+
+ final int index = findAutomaticOnOffKeepaliveIndex(network, slot);
+ return (index >= 0) ? mAutomaticOnOffKeepalives.get(index) : null;
}
/**
* Handle keepalive events from lower layer.
+ *
+ * Forward to KeepaliveTracker.
*/
public void handleEventSocketKeepalive(@NonNull NetworkAgentInfo nai, int slot, int reason) {
mKeepaliveTracker.handleEventSocketKeepalive(nai, slot, reason);
@@ -116,27 +329,86 @@
*/
public void handleStopAllKeepalives(NetworkAgentInfo nai, int reason) {
mKeepaliveTracker.handleStopAllKeepalives(nai, reason);
+ final Iterator<AutomaticOnOffKeepalive> iterator = mAutomaticOnOffKeepalives.iterator();
+ while (iterator.hasNext()) {
+ final AutomaticOnOffKeepalive autoKi = iterator.next();
+ if (autoKi.mKi.getNai() == nai) {
+ cleanupAutoOnOffKeepalive(autoKi);
+ iterator.remove();
+ }
+ }
}
/**
- * Handle start keepalives with the message.
+ * Handle start keepalive contained within a message.
*
- * The message is expected to be a KeepaliveTracker.KeepaliveInfo.
+ * The message is expected to contain a KeepaliveTracker.KeepaliveInfo.
*/
public void handleStartKeepalive(Message message) {
mKeepaliveTracker.handleStartKeepalive(message);
+
+ // Add automatic on/off request into list to track its life cycle.
+ final boolean automaticOnOff = message.arg1 != 0;
+ if (automaticOnOff) {
+ final KeepaliveTracker.KeepaliveInfo ki = (KeepaliveTracker.KeepaliveInfo) message.obj;
+ AutomaticOnOffKeepalive autoKi;
+ try {
+ // CAREFUL : mKeepaliveTracker.handleStartKeepalive will assign |ki.mSlot| after
+ // pulling |ki| from the message. The constructor below will read this member
+ // (through ki.getSlot()) and therefore actively relies on handleStartKeepalive
+ // having assigned this member before this is called.
+ // TODO : clean this up by assigning the slot at the start of this method instead
+ // and ideally removing the mSlot member from KeepaliveInfo.
+ autoKi = new AutomaticOnOffKeepalive(ki, mContext);
+ } catch (SocketKeepalive.InvalidSocketException | IllegalArgumentException e) {
+ Log.e(TAG, "Fail to construct keepalive", e);
+ mKeepaliveTracker.notifyErrorCallback(ki.mCallback, ERROR_INVALID_SOCKET);
+ return;
+ }
+ mAutomaticOnOffKeepalives.add(autoKi);
+ startTcpPollingAlarm(autoKi.mTcpPollingAlarm);
+ }
+ }
+
+ private void handleResumeKeepalive(Message message) {
+ mKeepaliveTracker.handleStartKeepalive(message);
+ }
+
+ private void handleSuspendKeepalive(NetworkAgentInfo nai, int slot, int reason) {
+ mKeepaliveTracker.handleStopKeepalive(nai, slot, reason);
}
/**
* Handle stop keepalives on the specific network with given slot.
*/
public void handleStopKeepalive(NetworkAgentInfo nai, int slot, int reason) {
- mKeepaliveTracker.handleStopKeepalive(nai, slot, reason);
+ final AutomaticOnOffKeepalive autoKi = findAutomaticOnOffKeepalive(nai.network, slot);
+
+ // Let the original keepalive do the stop first, and then clean up the keepalive if it's an
+ // automatic keepalive.
+ if (autoKi == null || autoKi.mAutomaticOnOffState == STATE_ENABLED) {
+ mKeepaliveTracker.handleStopKeepalive(nai, slot, reason);
+ }
+
+ // Not an AutomaticOnOffKeepalive.
+ if (autoKi == null) return;
+
+ cleanupAutoOnOffKeepalive(autoKi);
+ mAutomaticOnOffKeepalives.remove(autoKi);
+ }
+
+ private void cleanupAutoOnOffKeepalive(@NonNull final AutomaticOnOffKeepalive autoKi) {
+ ensureRunningOnHandlerThread();
+ mAlarmManager.cancel(autoKi.mTcpPollingAlarm);
+ // Close the duplicated fd that maintains the lifecycle of socket.
+ FileUtils.closeQuietly(autoKi.mFd);
}
/**
* Called when requesting that keepalives be started on a IPsec NAT-T socket. See
* {@link android.net.SocketKeepalive}.
+ *
+ * Forward to KeepaliveTracker.
**/
public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
@Nullable FileDescriptor fd,
@@ -145,14 +417,21 @@
@NonNull String srcAddrString,
int srcPort,
@NonNull String dstAddrString,
- int dstPort) {
- mKeepaliveTracker.startNattKeepalive(nai, fd, intervalSeconds, cb, srcAddrString,
- srcPort, dstAddrString, dstPort);
+ int dstPort, boolean automaticOnOffKeepalives) {
+ final KeepaliveTracker.KeepaliveInfo ki = mKeepaliveTracker.makeNattKeepaliveInfo(nai, fd,
+ intervalSeconds, cb, srcAddrString, srcPort, dstAddrString, dstPort);
+ if (null != ki) {
+ mConnectivityServiceHandler.obtainMessage(NetworkAgent.CMD_START_SOCKET_KEEPALIVE,
+ // TODO : move ConnectivityService#encodeBool to a static lib.
+ automaticOnOffKeepalives ? 1 : 0, 0, ki).sendToTarget();
+ }
}
/**
* Called when requesting that keepalives be started on a IPsec NAT-T socket. See
* {@link android.net.SocketKeepalive}.
+ *
+ * Forward to KeepaliveTracker.
**/
public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
@Nullable FileDescriptor fd,
@@ -161,9 +440,15 @@
@NonNull ISocketKeepaliveCallback cb,
@NonNull String srcAddrString,
@NonNull String dstAddrString,
- int dstPort) {
- mKeepaliveTracker.startNattKeepalive(nai, fd, resourceId, intervalSeconds, cb,
- srcAddrString, dstAddrString, dstPort);
+ int dstPort,
+ boolean automaticOnOffKeepalives) {
+ final KeepaliveTracker.KeepaliveInfo ki = mKeepaliveTracker.makeNattKeepaliveInfo(nai, fd,
+ resourceId, intervalSeconds, cb, srcAddrString, dstAddrString, dstPort);
+ if (null != ki) {
+ mConnectivityServiceHandler.obtainMessage(NetworkAgent.CMD_START_SOCKET_KEEPALIVE,
+ // TODO : move ConnectivityService#encodeBool to a static lib.
+ automaticOnOffKeepalives ? 1 : 0, 0, ki).sendToTarget();
+ }
}
/**
@@ -173,26 +458,34 @@
* other fields are needed to form the keepalive packet. Thus, this function synchronously
* puts the socket into repair mode to get the necessary information. After the socket has been
* put into repair mode, the application cannot access the socket until reverted to normal.
- *
* See {@link android.net.SocketKeepalive}.
+ *
+ * Forward to KeepaliveTracker.
**/
public void startTcpKeepalive(@Nullable NetworkAgentInfo nai,
@NonNull FileDescriptor fd,
int intervalSeconds,
@NonNull ISocketKeepaliveCallback cb) {
- mKeepaliveTracker.startTcpKeepalive(nai, fd, intervalSeconds, cb);
+ final KeepaliveTracker.KeepaliveInfo ki = mKeepaliveTracker.makeTcpKeepaliveInfo(nai, fd,
+ intervalSeconds, cb);
+ if (null != ki) {
+ mConnectivityServiceHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, ki)
+ .sendToTarget();
+ }
}
/**
* Dump AutomaticOnOffKeepaliveTracker state.
*/
public void dump(IndentingPrintWriter pw) {
- // TODO: Dump the necessary information for automatic on/off keepalive.
+ // TODO: Dump the necessary information for automatic on/off keepalive.
mKeepaliveTracker.dump(pw);
}
/**
- * Check all keeplaives on the network are still valid.
+ * Check all keepalives on the network are still valid.
+ *
+ * Forward to KeepaliveTracker.
*/
public void handleCheckKeepalivesStillValid(NetworkAgentInfo nai) {
mKeepaliveTracker.handleCheckKeepalivesStillValid(nai);
diff --git a/service/src/com/android/server/connectivity/KeepaliveTracker.java b/service/src/com/android/server/connectivity/KeepaliveTracker.java
index 23fdfd4..03f8f3e 100644
--- a/service/src/com/android/server/connectivity/KeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveTracker.java
@@ -18,7 +18,6 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.NattSocketKeepalive.NATT_PORT;
-import static android.net.NetworkAgent.CMD_START_SOCKET_KEEPALIVE;
import static android.net.SocketKeepalive.BINDER_DIED;
import static android.net.SocketKeepalive.DATA_RECEIVED;
import static android.net.SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES;
@@ -88,7 +87,6 @@
/** Keeps track of keepalive requests. */
private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives =
new HashMap<> ();
- private final Handler mConnectivityServiceHandler;
@NonNull
private final TcpKeepaliveController mTcpController;
@NonNull
@@ -109,7 +107,6 @@
private final int mAllowedUnprivilegedSlotsForUid;
public KeepaliveTracker(Context context, Handler handler) {
- mConnectivityServiceHandler = handler;
mTcpController = new TcpKeepaliveController(handler);
mContext = context;
@@ -130,13 +127,13 @@
*/
class KeepaliveInfo implements IBinder.DeathRecipient {
// Bookkeeping data.
- private final ISocketKeepaliveCallback mCallback;
+ public final ISocketKeepaliveCallback mCallback;
private final int mUid;
private final int mPid;
private final boolean mPrivileged;
- private final NetworkAgentInfo mNai;
+ public final NetworkAgentInfo mNai;
private final int mType;
- private final FileDescriptor mFd;
+ public final FileDescriptor mFd;
public static final int TYPE_NATT = 1;
public static final int TYPE_TCP = 2;
@@ -244,6 +241,10 @@
}
}
+ public int getSlot() {
+ return mSlot;
+ }
+
private int checkNetworkConnected() {
if (!mNai.networkInfo.isConnectedOrConnecting()) {
return ERROR_INVALID_NETWORK;
@@ -416,6 +417,13 @@
void onFileDescriptorInitiatedStop(final int socketKeepaliveReason) {
handleStopKeepalive(mNai, mSlot, socketKeepaliveReason);
}
+
+ /**
+ * Construct a new KeepaliveInfo from existing KeepaliveInfo with a new fd.
+ */
+ public KeepaliveInfo withFd(@NonNull FileDescriptor fd) throws InvalidSocketException {
+ return new KeepaliveInfo(mCallback, mNai, mPacket, mInterval, mType, fd);
+ }
}
void notifyErrorCallback(ISocketKeepaliveCallback cb, int error) {
@@ -445,6 +453,9 @@
return slot;
}
+ /**
+ * Handle start keepalives with the message.
+ */
public void handleStartKeepalive(Message message) {
KeepaliveInfo ki = (KeepaliveInfo) message.obj;
NetworkAgentInfo nai = ki.getNai();
@@ -605,7 +616,8 @@
* Called when requesting that keepalives be started on a IPsec NAT-T socket. See
* {@link android.net.SocketKeepalive}.
**/
- public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+ @Nullable
+ public KeepaliveInfo makeNattKeepaliveInfo(@Nullable NetworkAgentInfo nai,
@Nullable FileDescriptor fd,
int intervalSeconds,
@NonNull ISocketKeepaliveCallback cb,
@@ -615,7 +627,7 @@
int dstPort) {
if (nai == null) {
notifyErrorCallback(cb, ERROR_INVALID_NETWORK);
- return;
+ return null;
}
InetAddress srcAddress, dstAddress;
@@ -624,7 +636,7 @@
dstAddress = InetAddresses.parseNumericAddress(dstAddrString);
} catch (IllegalArgumentException e) {
notifyErrorCallback(cb, ERROR_INVALID_IP_ADDRESS);
- return;
+ return null;
}
KeepalivePacketData packet;
@@ -633,7 +645,7 @@
srcAddress, srcPort, dstAddress, NATT_PORT);
} catch (InvalidPacketException e) {
notifyErrorCallback(cb, e.getError());
- return;
+ return null;
}
KeepaliveInfo ki = null;
try {
@@ -642,15 +654,14 @@
} catch (InvalidSocketException | IllegalArgumentException | SecurityException e) {
Log.e(TAG, "Fail to construct keepalive", e);
notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
- return;
+ return null;
}
- Log.d(TAG, "Created keepalive: " + ki.toString());
- mConnectivityServiceHandler.obtainMessage(
- NetworkAgent.CMD_START_SOCKET_KEEPALIVE, ki).sendToTarget();
+ Log.d(TAG, "Created keepalive: " + ki);
+ return ki;
}
/**
- * Called by ConnectivityService to start TCP keepalive on a file descriptor.
+ * Make a KeepaliveInfo for a TCP socket.
*
* In order to offload keepalive for application correctly, sequence number, ack number and
* other fields are needed to form the keepalive packet. Thus, this function synchronously
@@ -659,13 +670,14 @@
*
* See {@link android.net.SocketKeepalive}.
**/
- public void startTcpKeepalive(@Nullable NetworkAgentInfo nai,
+ @Nullable
+ public KeepaliveInfo makeTcpKeepaliveInfo(@Nullable NetworkAgentInfo nai,
@NonNull FileDescriptor fd,
int intervalSeconds,
@NonNull ISocketKeepaliveCallback cb) {
if (nai == null) {
notifyErrorCallback(cb, ERROR_INVALID_NETWORK);
- return;
+ return null;
}
final TcpKeepalivePacketData packet;
@@ -673,10 +685,10 @@
packet = TcpKeepaliveController.getTcpKeepalivePacket(fd);
} catch (InvalidSocketException e) {
notifyErrorCallback(cb, e.error);
- return;
+ return null;
} catch (InvalidPacketException e) {
notifyErrorCallback(cb, e.getError());
- return;
+ return null;
}
KeepaliveInfo ki = null;
try {
@@ -685,20 +697,22 @@
} catch (InvalidSocketException | IllegalArgumentException | SecurityException e) {
Log.e(TAG, "Fail to construct keepalive e=" + e);
notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
- return;
+ return null;
}
Log.d(TAG, "Created keepalive: " + ki.toString());
- mConnectivityServiceHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, ki).sendToTarget();
+ return ki;
}
- /**
- * Called when requesting that keepalives be started on a IPsec NAT-T socket. This function is
- * identical to {@link #startNattKeepalive}, but also takes a {@code resourceId}, which is the
- * resource index bound to the {@link UdpEncapsulationSocket} when creating by
- * {@link com.android.server.IpSecService} to verify whether the given
- * {@link UdpEncapsulationSocket} is legitimate.
- **/
- public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+ /**
+ * Make a KeepaliveInfo for an IPSec NAT-T socket.
+ *
+ * This function is identical to {@link #makeNattKeepaliveInfo}, but also takes a
+ * {@code resourceId}, which is the resource index bound to the {@link UdpEncapsulationSocket}
+ * when creating by {@link com.android.server.IpSecService} to verify whether the given
+ * {@link UdpEncapsulationSocket} is legitimate.
+ **/
+ @Nullable
+ public KeepaliveInfo makeNattKeepaliveInfo(@Nullable NetworkAgentInfo nai,
@Nullable FileDescriptor fd,
int resourceId,
int intervalSeconds,
@@ -709,6 +723,7 @@
// Ensure that the socket is created by IpSecService.
if (!isNattKeepaliveSocketValid(fd, resourceId)) {
notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
+ return null;
}
// Get src port to adopt old API.
@@ -718,10 +733,11 @@
srcPort = ((InetSocketAddress) srcSockAddr).getPort();
} catch (ErrnoException e) {
notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
+ return null;
}
// Forward request to old API.
- startNattKeepalive(nai, fd, intervalSeconds, cb, srcAddrString, srcPort,
+ return makeNattKeepaliveInfo(nai, fd, intervalSeconds, cb, srcAddrString, srcPort,
dstAddrString, dstPort);
}