[DK2]Add new SocketKeepalive.start to dynamically control keepalive

Add SocketKeepalive.start with parameter to enable dynamic
keepalive mode based on the existence of TCP connections.

This supports IPSec mode to notify KeepaliveTracker to disable
keepalive when keepalive is unnecessary to improve battery life.

Keepalive is controlled by periodically TCP socket status check
for both enable and disable. This is a transition commit and
is expected to be updated based on the socket creation or
destroy.

Bug: 259000745
Test: m ; atest FrameworksNetTests
Change-Id: Ie4d598d69a73c4931c7d0b6dfde0e459e5dca6b4
This commit is contained in:
chiachangwang
2023-01-18 01:19:27 +00:00
committed by Chalard Jean
parent 3d60bacfa0
commit 9ef4ffe8d4
10 changed files with 504 additions and 68 deletions

View File

@@ -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"

View File

@@ -470,7 +470,9 @@ package android.net {
}
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
}

View File

@@ -188,7 +188,7 @@ interface IConnectivityManager
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);

View File

@@ -47,13 +47,39 @@ public final class NattSocketKeepalive extends SocketKeepalive {
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();

View File

@@ -483,6 +483,20 @@ public abstract class NetworkAgent {
*/
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);

View File

@@ -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;
@@ -173,6 +175,27 @@ public abstract class SocketKeepalive implements AutoCloseable {
})
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.
*
@@ -294,13 +317,15 @@ public abstract class SocketKeepalive implements AutoCloseable {
}
/**
* 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 abstract class SocketKeepalive implements AutoCloseable {
*/
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}

View File

@@ -50,7 +50,11 @@ public final class TcpSocketKeepalive extends SocketKeepalive {
* 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);

View File

@@ -101,7 +101,6 @@ import static com.android.net.module.util.NetworkMonitorUtils.isPrivateDnsValida
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;
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 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
private void enforceKeepalivePermission() {
mContext.enforceCallingOrSelfPermission(PERMISSION, "ConnectivityService");
mContext.enforceCallingOrSelfPermission(KeepaliveTracker.PERMISSION, "ConnectivityService");
}
private boolean checkLocalMacAddressPermission(int pid, int uid) {
@@ -5545,6 +5545,33 @@ public class ConnectivityService extends IConnectivityManager.Stub
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 @@ public class ConnectivityService extends IConnectivityManager.Stub
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.

View File

@@ -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.SOCKDIAG_MSG_
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 android.util.SparseArray;
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 com.android.net.module.util.netlink.StructNlAttr;
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 @@ public class AutomaticOnOffKeepaliveTracker {
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 @@ public class AutomaticOnOffKeepaliveTracker {
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 class AutomaticOnOffKeepaliveTracker {
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 class AutomaticOnOffKeepaliveTracker {
*/
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 @@ public class AutomaticOnOffKeepaliveTracker {
@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 @@ public class AutomaticOnOffKeepaliveTracker {
@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 @@ public class AutomaticOnOffKeepaliveTracker {
* 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);

View File

@@ -18,7 +18,6 @@ package com.android.server.connectivity;
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 @@ public class KeepaliveTracker {
/** 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 @@ public class KeepaliveTracker {
private final int mAllowedUnprivilegedSlotsForUid;
public KeepaliveTracker(Context context, Handler handler) {
mConnectivityServiceHandler = handler;
mTcpController = new TcpKeepaliveController(handler);
mContext = context;
@@ -130,13 +127,13 @@ public class KeepaliveTracker {
*/
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 class KeepaliveTracker {
}
}
public int getSlot() {
return mSlot;
}
private int checkNetworkConnected() {
if (!mNai.networkInfo.isConnectedOrConnecting()) {
return ERROR_INVALID_NETWORK;
@@ -416,6 +417,13 @@ public class KeepaliveTracker {
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 @@ public class KeepaliveTracker {
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 @@ public class KeepaliveTracker {
* 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 @@ public class KeepaliveTracker {
int dstPort) {
if (nai == null) {
notifyErrorCallback(cb, ERROR_INVALID_NETWORK);
return;
return null;
}
InetAddress srcAddress, dstAddress;
@@ -624,7 +636,7 @@ public class KeepaliveTracker {
dstAddress = InetAddresses.parseNumericAddress(dstAddrString);
} catch (IllegalArgumentException e) {
notifyErrorCallback(cb, ERROR_INVALID_IP_ADDRESS);
return;
return null;
}
KeepalivePacketData packet;
@@ -633,7 +645,7 @@ public class KeepaliveTracker {
srcAddress, srcPort, dstAddress, NATT_PORT);
} catch (InvalidPacketException e) {
notifyErrorCallback(cb, e.getError());
return;
return null;
}
KeepaliveInfo ki = null;
try {
@@ -642,15 +654,14 @@ public class KeepaliveTracker {
} 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 @@ public class KeepaliveTracker {
*
* 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 @@ public class KeepaliveTracker {
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 @@ public class KeepaliveTracker {
} 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 @@ public class KeepaliveTracker {
// 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 @@ public class KeepaliveTracker {
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);
}