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

This commit is contained in:
Jean Chalard 2023-01-20 11:55:06 +00:00 • committed by Gerrit Code Review
commit a31dc31b20
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);
}