Merge "Add nullability annotations"
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
index ebd8a7e..db19515 100644
--- a/core/java/android/net/CaptivePortal.java
+++ b/core/java/android/net/CaptivePortal.java
@@ -64,8 +64,6 @@
private final IBinder mBinder;
/** @hide */
- @SystemApi
- @TestApi
public CaptivePortal(@NonNull IBinder binder) {
mBinder = binder;
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 1632235..ae93cf0 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -44,6 +44,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
+import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -64,6 +65,8 @@
import libcore.net.event.NetworkEventDispatcher;
import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.UncheckedIOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.InetAddress;
@@ -1345,12 +1348,15 @@
}
/**
- * Gets the URL that should be used for resolving whether a captive portal is present.
+ * Gets a URL that can be used for resolving whether a captive portal is present.
* 1. This URL should respond with a 204 response to a GET request to indicate no captive
* portal is present.
* 2. This URL must be HTTP as redirect responses are used to find captive portal
* sign-in pages. Captive portals cannot respond to HTTPS requests with redirects.
*
+ * The system network validation may be using different strategies to detect captive portals,
+ * so this method does not necessarily return a URL used by the system. It only returns a URL
+ * that may be relevant for other components trying to detect captive portals.
* @hide
*/
@SystemApi
@@ -1920,14 +1926,22 @@
* @return A {@link SocketKeepalive} object that can be used to control the keepalive on the
* given socket.
**/
- public SocketKeepalive createSocketKeepalive(@NonNull Network network,
+ public @NonNull SocketKeepalive createSocketKeepalive(@NonNull Network network,
@NonNull UdpEncapsulationSocket socket,
@NonNull InetAddress source,
@NonNull InetAddress destination,
@NonNull @CallbackExecutor Executor executor,
@NonNull Callback callback) {
- return new NattSocketKeepalive(mService, network, socket.getFileDescriptor(),
- socket.getResourceId(), source, destination, executor, callback);
+ ParcelFileDescriptor dup;
+ try {
+ dup = ParcelFileDescriptor.dup(socket.getFileDescriptor());
+ } catch (IOException ignored) {
+ // Construct an invalid fd, so that if the user later calls start(), it will fail with
+ // ERROR_INVALID_SOCKET.
+ dup = new ParcelFileDescriptor(new FileDescriptor());
+ }
+ return new NattSocketKeepalive(mService, network, dup, socket.getResourceId(), source,
+ destination, executor, callback);
}
/**
@@ -1935,9 +1949,9 @@
* by system apps which don't use IpSecService to create {@link UdpEncapsulationSocket}.
*
* @param network The {@link Network} the socket is on.
- * @param fd The {@link FileDescriptor} that needs to be kept alive. The provided
- * {@link FileDescriptor} must be bound to a port and the keepalives will be sent from
- * that port.
+ * @param pfd The {@link ParcelFileDescriptor} that needs to be kept alive. The provided
+ * {@link ParcelFileDescriptor} must be bound to a port and the keepalives will be sent
+ * from that port.
* @param source The source address of the {@link UdpEncapsulationSocket}.
* @param destination The destination address of the {@link UdpEncapsulationSocket}. The
* keepalive packets will always be sent to port 4500 of the given {@code destination}.
@@ -1953,14 +1967,22 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD)
- public SocketKeepalive createNattKeepalive(@NonNull Network network,
- @NonNull FileDescriptor fd,
+ public @NonNull SocketKeepalive createNattKeepalive(@NonNull Network network,
+ @NonNull ParcelFileDescriptor pfd,
@NonNull InetAddress source,
@NonNull InetAddress destination,
@NonNull @CallbackExecutor Executor executor,
@NonNull Callback callback) {
- return new NattSocketKeepalive(mService, network, fd, INVALID_RESOURCE_ID /* Unused */,
- source, destination, executor, callback);
+ ParcelFileDescriptor dup;
+ try {
+ dup = pfd.dup();
+ } catch (IOException ignored) {
+ // Construct an invalid fd, so that if the user later calls start(), it will fail with
+ // ERROR_INVALID_SOCKET.
+ dup = new ParcelFileDescriptor(new FileDescriptor());
+ }
+ return new NattSocketKeepalive(mService, network, dup,
+ INVALID_RESOURCE_ID /* Unused */, source, destination, executor, callback);
}
/**
@@ -1984,11 +2006,19 @@
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD)
- public SocketKeepalive createSocketKeepalive(@NonNull Network network,
+ public @NonNull SocketKeepalive createSocketKeepalive(@NonNull Network network,
@NonNull Socket socket,
@NonNull Executor executor,
@NonNull Callback callback) {
- return new TcpSocketKeepalive(mService, network, socket, executor, callback);
+ ParcelFileDescriptor dup;
+ try {
+ dup = ParcelFileDescriptor.fromSocket(socket);
+ } catch (UncheckedIOException ignored) {
+ // Construct an invalid fd, so that if the user later calls start(), it will fail with
+ // ERROR_INVALID_SOCKET.
+ dup = new ParcelFileDescriptor(new FileDescriptor());
+ }
+ return new TcpSocketKeepalive(mService, network, dup, executor, callback);
}
/**
@@ -3320,7 +3350,7 @@
* @param network The {@link Network} whose blocked status has changed.
* @param blocked The blocked status of this {@link Network}.
*/
- public void onBlockedStatusChanged(Network network, boolean blocked) {}
+ public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {}
private NetworkRequest networkRequest;
}
@@ -4101,9 +4131,12 @@
* @hide
*/
@SystemApi
- public boolean getAvoidBadWifi() {
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK})
+ public boolean shouldAvoidBadWifi() {
try {
- return mService.getAvoidBadWifi();
+ return mService.shouldAvoidBadWifi();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java
index 93b8cf8..5980251 100644
--- a/core/java/android/net/DnsResolver.java
+++ b/core/java/android/net/DnsResolver.java
@@ -16,6 +16,7 @@
package android.net;
+import static android.net.NetworkUtils.resNetworkCancel;
import static android.net.NetworkUtils.resNetworkQuery;
import static android.net.NetworkUtils.resNetworkResult;
import static android.net.NetworkUtils.resNetworkSend;
@@ -26,6 +27,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.CancellationSignal;
import android.os.Looper;
import android.system.ErrnoException;
import android.util.Log;
@@ -191,11 +193,18 @@
* @param query blob message
* @param flags flags as a combination of the FLAGS_* constants
* @param executor The {@link Executor} that the callback should be executed on.
+ * @param cancellationSignal used by the caller to signal if the query should be
+ * cancelled. May be {@code null}.
* @param callback an {@link AnswerCallback} which will be called to notify the caller
- * of the result of dns query.
+ * of the result of dns query.
*/
public <T> void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
- @NonNull @CallbackExecutor Executor executor, @NonNull AnswerCallback<T> callback) {
+ @NonNull @CallbackExecutor Executor executor,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull AnswerCallback<T> callback) {
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ return;
+ }
final FileDescriptor queryfd;
try {
queryfd = resNetworkSend((network != null
@@ -205,6 +214,7 @@
return;
}
+ maybeAddCancellationSignal(cancellationSignal, queryfd);
registerFDListener(executor, queryfd, callback);
}
@@ -219,12 +229,19 @@
* @param nsType dns resource record (RR) type as one of the TYPE_* constants
* @param flags flags as a combination of the FLAGS_* constants
* @param executor The {@link Executor} that the callback should be executed on.
+ * @param cancellationSignal used by the caller to signal if the query should be
+ * cancelled. May be {@code null}.
* @param callback an {@link AnswerCallback} which will be called to notify the caller
- * of the result of dns query.
+ * of the result of dns query.
*/
public <T> void query(@Nullable Network network, @NonNull String domain,
@QueryClass int nsClass, @QueryType int nsType, @QueryFlag int flags,
- @NonNull @CallbackExecutor Executor executor, @NonNull AnswerCallback<T> callback) {
+ @NonNull @CallbackExecutor Executor executor,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull AnswerCallback<T> callback) {
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ return;
+ }
final FileDescriptor queryfd;
try {
queryfd = resNetworkQuery((network != null
@@ -233,6 +250,8 @@
callback.onQueryException(e);
return;
}
+
+ maybeAddCancellationSignal(cancellationSignal, queryfd);
registerFDListener(executor, queryfd, callback);
}
@@ -264,6 +283,17 @@
});
}
+ private void maybeAddCancellationSignal(@Nullable CancellationSignal cancellationSignal,
+ @NonNull FileDescriptor queryfd) {
+ if (cancellationSignal == null) return;
+ cancellationSignal.setOnCancelListener(
+ () -> {
+ Looper.getMainLooper().getQueue()
+ .removeOnFileDescriptorEventListener(queryfd);
+ resNetworkCancel(queryfd);
+ });
+ }
+
private static class DnsAddressAnswer extends DnsPacket {
private static final String TAG = "DnsResolver.DnsAddressAnswer";
private static final boolean DBG = false;
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 24e6a85..61648dc 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -182,7 +182,7 @@
void startCaptivePortalApp(in Network network);
void startCaptivePortalAppInternal(in Network network, in Bundle appExtras);
- boolean getAvoidBadWifi();
+ boolean shouldAvoidBadWifi();
int getMultipathPreference(in Network Network);
NetworkRequest getDefaultRequest();
diff --git a/core/java/android/net/NattSocketKeepalive.java b/core/java/android/net/NattSocketKeepalive.java
index 84da294..b0ce0c7 100644
--- a/core/java/android/net/NattSocketKeepalive.java
+++ b/core/java/android/net/NattSocketKeepalive.java
@@ -17,10 +17,10 @@
package android.net;
import android.annotation.NonNull;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
-import java.io.FileDescriptor;
import java.net.InetAddress;
import java.util.concurrent.Executor;
@@ -31,21 +31,19 @@
@NonNull private final InetAddress mSource;
@NonNull private final InetAddress mDestination;
- @NonNull private final FileDescriptor mFd;
private final int mResourceId;
NattSocketKeepalive(@NonNull IConnectivityManager service,
@NonNull Network network,
- @NonNull FileDescriptor fd,
+ @NonNull ParcelFileDescriptor pfd,
int resourceId,
@NonNull InetAddress source,
@NonNull InetAddress destination,
@NonNull Executor executor,
@NonNull Callback callback) {
- super(service, network, executor, callback);
+ super(service, network, pfd, executor, callback);
mSource = source;
mDestination = destination;
- mFd = fd;
mResourceId = resourceId;
}
@@ -53,8 +51,8 @@
void startImpl(int intervalSec) {
mExecutor.execute(() -> {
try {
- mService.startNattKeepaliveWithFd(mNetwork, mFd, mResourceId, intervalSec,
- mCallback,
+ mService.startNattKeepaliveWithFd(mNetwork, mPfd.getFileDescriptor(), mResourceId,
+ intervalSec, mCallback,
mSource.getHostAddress(), mDestination.getHostAddress());
} catch (RemoteException e) {
Log.e(TAG, "Error starting socket keepalive: ", e);
@@ -75,6 +73,5 @@
throw e.rethrowFromSystemServer();
}
});
-
}
}
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index b55f6ba..1edea55 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -488,14 +488,14 @@
* Requests that the network hardware send the specified packet at the specified interval.
*/
protected void startSocketKeepalive(Message msg) {
- onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
+ onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
}
/**
* Requests that the network hardware stops sending keepalive packets.
*/
protected void stopSocketKeepalive(Message msg) {
- onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
+ onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
}
/**
@@ -511,7 +511,7 @@
* override this method.
*/
protected void addKeepalivePacketFilter(Message msg) {
- onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
+ onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
}
/**
@@ -520,7 +520,7 @@
* must override this method.
*/
protected void removeKeepalivePacketFilter(Message msg) {
- onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
+ onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
}
/**
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 0ae29b1..d2d886b 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -172,6 +172,12 @@
public static native byte[] resNetworkResult(FileDescriptor fd) throws ErrnoException;
/**
+ * DNS resolver series jni method.
+ * Attempts to cancel the in-progress query associated with the {@code fd}.
+ */
+ public static native void resNetworkCancel(FileDescriptor fd);
+
+ /**
* Add an entry into the ARP cache.
*/
public static void addArpEntry(Inet4Address ipv4Addr, MacAddress ethAddr, String ifname,
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
index 0e768df..9d91620 100644
--- a/core/java/android/net/SocketKeepalive.java
+++ b/core/java/android/net/SocketKeepalive.java
@@ -21,8 +21,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Binder;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;
@@ -73,10 +75,15 @@
/** The target socket is not idle. */
public static final int ERROR_SOCKET_NOT_IDLE = -26;
- /** The hardware does not support this request. */
- public static final int ERROR_HARDWARE_UNSUPPORTED = -30;
+ /** The device does not support this request. */
+ public static final int ERROR_UNSUPPORTED = -30;
+ /** @hide TODO: delete when telephony code has been updated. */
+ public static final int ERROR_HARDWARE_UNSUPPORTED = ERROR_UNSUPPORTED;
/** The hardware returned an error. */
public static final int ERROR_HARDWARE_ERROR = -31;
+ /** The limitation of resource is reached. */
+ public static final int ERROR_INSUFFICIENT_RESOURCES = -32;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -147,15 +154,18 @@
@NonNull final IConnectivityManager mService;
@NonNull final Network mNetwork;
+ @NonNull final ParcelFileDescriptor mPfd;
@NonNull final Executor mExecutor;
@NonNull final ISocketKeepaliveCallback mCallback;
// TODO: remove slot since mCallback could be used to identify which keepalive to stop.
@Nullable Integer mSlot;
SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network,
+ @NonNull ParcelFileDescriptor pfd,
@NonNull Executor executor, @NonNull Callback callback) {
mService = service;
mNetwork = network;
+ mPfd = pfd;
mExecutor = executor;
mCallback = new ISocketKeepaliveCallback.Stub() {
@Override
@@ -233,6 +243,11 @@
@Override
public final void close() {
stop();
+ try {
+ mPfd.close();
+ } catch (IOException e) {
+ // Nothing much can be done.
+ }
}
/**
diff --git a/core/java/android/net/TcpSocketKeepalive.java b/core/java/android/net/TcpSocketKeepalive.java
index 26cc8ff..436397e 100644
--- a/core/java/android/net/TcpSocketKeepalive.java
+++ b/core/java/android/net/TcpSocketKeepalive.java
@@ -17,25 +17,22 @@
package android.net;
import android.annotation.NonNull;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
import java.io.FileDescriptor;
-import java.net.Socket;
import java.util.concurrent.Executor;
/** @hide */
final class TcpSocketKeepalive extends SocketKeepalive {
- private final Socket mSocket;
-
TcpSocketKeepalive(@NonNull IConnectivityManager service,
@NonNull Network network,
- @NonNull Socket socket,
+ @NonNull ParcelFileDescriptor pfd,
@NonNull Executor executor,
@NonNull Callback callback) {
- super(service, network, executor, callback);
- mSocket = socket;
+ super(service, network, pfd, executor, callback);
}
/**
@@ -57,7 +54,7 @@
void startImpl(int intervalSec) {
mExecutor.execute(() -> {
try {
- final FileDescriptor fd = mSocket.getFileDescriptor$();
+ final FileDescriptor fd = mPfd.getFileDescriptor();
mService.startTcpKeepalive(mNetwork, fd, intervalSec, mCallback);
} catch (RemoteException e) {
Log.e(TAG, "Error starting packet keepalive: ", e);
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index cfb2dd1..d7a981e 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -487,6 +487,11 @@
return answer;
}
+static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) {
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ resNetworkCancel(fd);
+}
+
static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) {
if (javaFd == NULL) {
jniThrowNullPointerException(env, NULL);
@@ -546,6 +551,7 @@
{ "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
{ "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
{ "resNetworkResult", "(Ljava/io/FileDescriptor;)[B", (void*) android_net_utils_resNetworkResult },
+ { "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel },
};
int register_android_net_NetworkUtils(JNIEnv* env)
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 52fcd4a..bca2df4 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -988,7 +988,7 @@
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- mKeepaliveTracker = new KeepaliveTracker(mHandler);
+ mKeepaliveTracker = new KeepaliveTracker(mContext, mHandler);
mNotifier = new NetworkNotificationManager(mContext, mTelephonyManager,
mContext.getSystemService(NotificationManager.class));
@@ -2564,7 +2564,12 @@
|| (nai.networkMisc.acceptPartialConnectivity
&& nai.partialConnectivity);
// Once a network is determined to have partial connectivity, it cannot
- // go back to full connectivity without a disconnect.
+ // go back to full connectivity without a disconnect. This is because
+ // NetworkMonitor can only communicate either PARTIAL_CONNECTIVITY or VALID,
+ // but not both.
+ // TODO: Provide multi-testResult to improve the communication between
+ // ConnectivityService and NetworkMonitor, so that ConnectivityService could
+ // know the real status of network.
final boolean partialConnectivityChanged =
(partialConnectivity && !nai.partialConnectivity);
@@ -3470,8 +3475,12 @@
return mMultinetworkPolicyTracker.getAvoidBadWifi();
}
- @Override
- public boolean getAvoidBadWifi() {
+ /**
+ * Return whether the device should maintain continuous, working connectivity by switching away
+ * from WiFi networks having no connectivity.
+ * @see MultinetworkPolicyTracker#getAvoidBadWifi()
+ */
+ public boolean shouldAvoidBadWifi() {
if (!checkNetworkStackPermission()) {
throw new SecurityException("avoidBadWifi requires NETWORK_STACK permission");
}
@@ -3581,9 +3590,7 @@
// NetworkMonitor detects the network is partial connectivity. Need to change the design to
// popup the notification immediately when the network is partial connectivity.
if (nai.partialConnectivity) {
- // Treat PARTIAL_CONNECTIVITY as NO_INTERNET temporary until Settings has been updated.
- // TODO: Need to change back to PARTIAL_CONNECTIVITY when Settings part is merged.
- showNetworkNotification(nai, NotificationType.NO_INTERNET);
+ showNetworkNotification(nai, NotificationType.PARTIAL_CONNECTIVITY);
} else {
showNetworkNotification(nai, NotificationType.NO_INTERNET);
}
@@ -6685,7 +6692,9 @@
@Override
public String getCaptivePortalServerUrl() {
enforceConnectivityInternalPermission();
- return NetworkMonitorUtils.getCaptivePortalServerHttpUrl(mContext);
+ final String defaultUrl = mContext.getResources().getString(
+ R.string.config_networkDefaultCaptivePortalServerUrl);
+ return NetworkMonitorUtils.getCaptivePortalServerHttpUrl(mContext, defaultUrl);
}
@Override
@@ -6693,7 +6702,7 @@
ISocketKeepaliveCallback cb, String srcAddr, int srcPort, String dstAddr) {
enforceKeepalivePermission();
mKeepaliveTracker.startNattKeepalive(
- getNetworkAgentInfoForNetwork(network),
+ getNetworkAgentInfoForNetwork(network), null /* fd */,
intervalSeconds, cb,
srcAddr, srcPort, dstAddr, NattSocketKeepalive.NATT_PORT);
}
@@ -6702,7 +6711,6 @@
public void startNattKeepaliveWithFd(Network network, FileDescriptor fd, int resourceId,
int intervalSeconds, ISocketKeepaliveCallback cb, String srcAddr,
String dstAddr) {
- enforceKeepalivePermission();
mKeepaliveTracker.startNattKeepalive(
getNetworkAgentInfoForNetwork(network), fd, resourceId,
intervalSeconds, cb,
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 0e3d82c..ce887eb 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -16,6 +16,7 @@
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_ADD_KEEPALIVE_PACKET_FILTER;
import static android.net.NetworkAgent.CMD_REMOVE_KEEPALIVE_PACKET_FILTER;
@@ -23,6 +24,7 @@
import static android.net.NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE;
import static android.net.SocketKeepalive.BINDER_DIED;
import static android.net.SocketKeepalive.DATA_RECEIVED;
+import static android.net.SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES;
import static android.net.SocketKeepalive.ERROR_INVALID_INTERVAL;
import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
import static android.net.SocketKeepalive.ERROR_INVALID_NETWORK;
@@ -34,6 +36,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
import android.net.ISocketKeepaliveCallback;
import android.net.KeepalivePacketData;
import android.net.NattKeepalivePacketData;
@@ -84,10 +87,13 @@
private final Handler mConnectivityServiceHandler;
@NonNull
private final TcpKeepaliveController mTcpController;
+ @NonNull
+ private final Context mContext;
- public KeepaliveTracker(Handler handler) {
+ public KeepaliveTracker(Context context, Handler handler) {
mConnectivityServiceHandler = handler;
mTcpController = new TcpKeepaliveController(handler);
+ mContext = context;
}
/**
@@ -101,6 +107,7 @@
private final ISocketKeepaliveCallback mCallback;
private final int mUid;
private final int mPid;
+ private final boolean mPrivileged;
private final NetworkAgentInfo mNai;
private final int mType;
private final FileDescriptor mFd;
@@ -108,6 +115,11 @@
public static final int TYPE_NATT = 1;
public static final int TYPE_TCP = 2;
+ // Max allowed unprivileged keepalive slots per network. Caller's permission will be
+ // enforced if number of existing keepalives reach this limit.
+ // TODO: consider making this limit configurable via resources.
+ private static final int MAX_UNPRIVILEGED_SLOTS = 3;
+
// Keepalive slot. A small integer that identifies this keepalive among the ones handled
// by this network.
private int mSlot = NO_KEEPALIVE;
@@ -127,16 +139,33 @@
@NonNull KeepalivePacketData packet,
int interval,
int type,
- @NonNull FileDescriptor fd) {
+ @Nullable FileDescriptor fd) throws InvalidSocketException {
mCallback = callback;
mPid = Binder.getCallingPid();
mUid = Binder.getCallingUid();
+ mPrivileged = (PERMISSION_GRANTED == mContext.checkPermission(PERMISSION, mPid, mUid));
mNai = nai;
mPacket = packet;
mInterval = interval;
mType = type;
- mFd = fd;
+
+ // For SocketKeepalive, a dup of fd is kept in mFd so the source port from which the
+ // keepalives are sent cannot be reused by another app even if the fd gets closed by
+ // the user. A null is acceptable here for backward compatibility of PacketKeepalive
+ // API.
+ // TODO: don't accept null fd after legacy packetKeepalive API is removed.
+ try {
+ if (fd != null) {
+ mFd = Os.dup(fd);
+ } else {
+ Log.d(TAG, "uid/pid " + mUid + "/" + mPid + " calls with null fd");
+ mFd = null;
+ }
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot dup fd: ", e);
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
+ }
try {
mCallback.asBinder().linkToDeath(this, 0);
@@ -167,7 +196,7 @@
+ "->"
+ IpUtils.addressAndPortToString(mPacket.dstAddress, mPacket.dstPort)
+ " interval=" + mInterval
- + " uid=" + mUid + " pid=" + mPid
+ + " uid=" + mUid + " pid=" + mPid + " privileged=" + mPrivileged
+ " packetData=" + HexDump.toHexString(mPacket.getPacket())
+ " ]";
}
@@ -207,9 +236,27 @@
return SUCCESS;
}
+ private int checkPermission() {
+ final HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(mNai);
+ int unprivilegedCount = 0;
+ if (networkKeepalives == null) {
+ return ERROR_INVALID_NETWORK;
+ }
+ for (KeepaliveInfo ki : networkKeepalives.values()) {
+ if (!ki.mPrivileged) {
+ unprivilegedCount++;
+ }
+ if (unprivilegedCount >= MAX_UNPRIVILEGED_SLOTS) {
+ return mPrivileged ? SUCCESS : ERROR_INSUFFICIENT_RESOURCES;
+ }
+ }
+ return SUCCESS;
+ }
+
private int isValid() {
synchronized (mNai) {
int error = checkInterval();
+ if (error == SUCCESS) error = checkPermission();
if (error == SUCCESS) error = checkNetworkConnected();
if (error == SUCCESS) error = checkSourceAddress();
return error;
@@ -272,6 +319,18 @@
}
}
+ // Close the duplicated fd that maintains the lifecycle of socket whenever
+ // keepalive is running.
+ if (mFd != null) {
+ try {
+ Os.close(mFd);
+ } catch (ErrnoException e) {
+ // This should not happen since system server controls the lifecycle of fd when
+ // keepalive offload is running.
+ Log.wtf(TAG, "Error closing fd for keepalive " + mSlot + ": " + e);
+ }
+ }
+
if (reason == SUCCESS) {
try {
mCallback.onStopped();
@@ -355,8 +414,9 @@
return;
}
ki.stop(reason);
- Log.d(TAG, "Stopped keepalive " + slot + " on " + networkName);
networkKeepalives.remove(slot);
+ Log.d(TAG, "Stopped keepalive " + slot + " on " + networkName + ", "
+ + networkKeepalives.size() + " remains.");
if (networkKeepalives.isEmpty()) {
mKeepalives.remove(nai);
}
@@ -389,7 +449,8 @@
ki = mKeepalives.get(nai).get(slot);
} catch(NullPointerException e) {}
if (ki == null) {
- Log.e(TAG, "Event for unknown keepalive " + slot + " on " + nai.name());
+ Log.e(TAG, "Event " + message.what + " for unknown keepalive " + slot + " on "
+ + nai.name());
return;
}
@@ -437,6 +498,7 @@
* {@link android.net.SocketKeepalive}.
**/
public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+ @Nullable FileDescriptor fd,
int intervalSeconds,
@NonNull ISocketKeepaliveCallback cb,
@NonNull String srcAddrString,
@@ -465,8 +527,14 @@
notifyErrorCallback(cb, e.error);
return;
}
- KeepaliveInfo ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds,
- KeepaliveInfo.TYPE_NATT, null);
+ KeepaliveInfo ki = null;
+ try {
+ ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds,
+ KeepaliveInfo.TYPE_NATT, fd);
+ } catch (InvalidSocketException e) {
+ notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
+ return;
+ }
Log.d(TAG, "Created keepalive: " + ki.toString());
mConnectivityServiceHandler.obtainMessage(
NetworkAgent.CMD_START_SOCKET_KEEPALIVE, ki).sendToTarget();
@@ -498,9 +566,14 @@
notifyErrorCallback(cb, e.error);
return;
}
-
- KeepaliveInfo ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds,
- KeepaliveInfo.TYPE_TCP, fd);
+ KeepaliveInfo ki = null;
+ try {
+ ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds,
+ KeepaliveInfo.TYPE_TCP, fd);
+ } catch (InvalidSocketException e) {
+ notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
+ return;
+ }
Log.d(TAG, "Created keepalive: " + ki.toString());
mConnectivityServiceHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, ki).sendToTarget();
}
@@ -535,7 +608,7 @@
}
// Forward request to old API.
- startNattKeepalive(nai, intervalSeconds, cb, srcAddrString, srcPort,
+ startNattKeepalive(nai, fd, intervalSeconds, cb, srcAddrString, srcPort,
dstAddrString, dstPort);
}
diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
index f4d9006..e570ef1 100644
--- a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
+++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
@@ -16,14 +16,17 @@
package com.android.server.connectivity;
import static android.net.SocketKeepalive.DATA_RECEIVED;
-import static android.net.SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
import static android.net.SocketKeepalive.ERROR_SOCKET_NOT_IDLE;
+import static android.net.SocketKeepalive.ERROR_UNSUPPORTED;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
import static android.system.OsConstants.ENOPROTOOPT;
import static android.system.OsConstants.FIONREAD;
+import static android.system.OsConstants.IPPROTO_IP;
import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IP_TOS;
+import static android.system.OsConstants.IP_TTL;
import static android.system.OsConstants.TIOCOUTQ;
import android.annotation.NonNull;
@@ -193,12 +196,18 @@
trw = NetworkUtils.getTcpRepairWindow(fd);
tcpDetails.rcvWnd = trw.rcvWnd;
tcpDetails.rcvWndScale = trw.rcvWndScale;
+ if (tcpDetails.srcAddress.length == 4 /* V4 address length */) {
+ // Query TOS.
+ tcpDetails.tos = Os.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
+ // Query TTL.
+ tcpDetails.ttl = Os.getsockoptInt(fd, IPPROTO_IP, IP_TTL);
+ }
} catch (ErrnoException e) {
Log.e(TAG, "Exception reading TCP state from socket", e);
if (e.errno == ENOPROTOOPT) {
// ENOPROTOOPT may happen in kernel version lower than 4.8.
- // Treat it as ERROR_HARDWARE_UNSUPPORTED.
- throw new InvalidSocketException(ERROR_HARDWARE_UNSUPPORTED, e);
+ // Treat it as ERROR_UNSUPPORTED.
+ throw new InvalidSocketException(ERROR_UNSUPPORTED, e);
} else {
throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
}
diff --git a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
index 372ffcd..e0b7227 100644
--- a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
+++ b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
@@ -48,6 +48,8 @@
final int ack = 0x22222222;
final int wnd = 8000;
final int wndScale = 2;
+ final int tos = 4;
+ final int ttl = 64;
TcpKeepalivePacketData resultData = null;
final TcpKeepalivePacketDataParcelable testInfo = new TcpKeepalivePacketDataParcelable();
testInfo.srcAddress = IPV4_KEEPALIVE_SRC_ADDR;
@@ -58,6 +60,8 @@
testInfo.ack = ack;
testInfo.rcvWnd = wnd;
testInfo.rcvWndScale = wndScale;
+ testInfo.tos = tos;
+ testInfo.ttl = ttl;
try {
resultData = TcpKeepalivePacketData.tcpKeepalivePacket(testInfo);
} catch (InvalidPacketException e) {
@@ -72,16 +76,21 @@
assertEquals(testInfo.ack, resultData.tcpAck);
assertEquals(testInfo.rcvWnd, resultData.tcpWnd);
assertEquals(testInfo.rcvWndScale, resultData.tcpWndScale);
+ assertEquals(testInfo.tos, resultData.ipTos);
+ assertEquals(testInfo.ttl, resultData.ipTtl);
TestUtils.assertParcelingIsLossless(resultData, TcpKeepalivePacketData.CREATOR);
final byte[] packet = resultData.getPacket();
- // IP version and TOS.
- ByteBuffer buf = ByteBuffer.wrap(packet);
- assertEquals(buf.getShort(), 0x4500);
+ // IP version and IHL
+ assertEquals(packet[0], 0x45);
+ // TOS
+ assertEquals(packet[1], tos);
+ // TTL
+ assertEquals(packet[8], ttl);
// Source IP address.
byte[] ip = new byte[4];
- buf = ByteBuffer.wrap(packet, 12, 4);
+ ByteBuffer buf = ByteBuffer.wrap(packet, 12, 4);
buf.get(ip);
assertArrayEquals(ip, IPV4_KEEPALIVE_SRC_ADDR);
// Destination IP address.
@@ -113,6 +122,8 @@
final int ack = 0x22222222;
final int wnd = 48_000;
final int wndScale = 2;
+ final int tos = 4;
+ final int ttl = 64;
final TcpKeepalivePacketDataParcelable testInfo = new TcpKeepalivePacketDataParcelable();
testInfo.srcAddress = IPV4_KEEPALIVE_SRC_ADDR;
testInfo.srcPort = srcPort;
@@ -122,6 +133,8 @@
testInfo.ack = ack;
testInfo.rcvWnd = wnd;
testInfo.rcvWndScale = wndScale;
+ testInfo.tos = tos;
+ testInfo.ttl = ttl;
TcpKeepalivePacketData testData = null;
TcpKeepalivePacketDataParcelable resultData = null;
testData = TcpKeepalivePacketData.tcpKeepalivePacket(testInfo);
@@ -134,5 +147,7 @@
assertEquals(resultData.ack, ack);
assertEquals(resultData.rcvWnd, wnd);
assertEquals(resultData.rcvWndScale, wndScale);
+ assertEquals(resultData.tos, tos);
+ assertEquals(resultData.ttl, ttl);
}
}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index d3616aa..92a865a 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -144,12 +144,14 @@
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.system.Os;
import android.test.mock.MockContentResolver;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -188,6 +190,8 @@
import org.mockito.Spy;
import org.mockito.stubbing.Answer;
+import java.io.IOException;
+import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -421,7 +425,7 @@
private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
private int mScore;
private NetworkAgent mNetworkAgent;
- private int mStartKeepaliveError = SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
+ private int mStartKeepaliveError = SocketKeepalive.ERROR_UNSUPPORTED;
private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE;
private Integer mExpectedKeepaliveSlot = null;
// Contains the redirectUrl from networkStatus(). Before reading, wait for
@@ -4032,6 +4036,7 @@
runTestWithSerialExecutors(executor -> {
try {
doTestNattSocketKeepalivesWithExecutor(executor);
+ doTestNattSocketKeepalivesFdWithExecutor(executor);
} catch (Exception e) {
fail(e.getMessage());
}
@@ -4041,6 +4046,8 @@
private void doTestNattSocketKeepalivesWithExecutor(Executor executor) throws Exception {
// TODO: 1. Move this outside of ConnectivityServiceTest.
// 2. Make test to verify that Nat-T keepalive socket is created by IpSecService.
+ // 3. Mock ipsec service.
+ // 4. Find a free port instead of a fixed port.
final int srcPort = 12345;
final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
final InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35");
@@ -4065,89 +4072,106 @@
Network myNet = connectKeepaliveNetwork(lp);
TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(executor);
- SocketKeepalive ka;
// Attempt to start keepalives with invalid parameters and check for errors.
// Invalid network.
- ka = mCm.createSocketKeepalive(notMyNet, testSocket, myIPv4, dstIPv4, executor, callback);
- ka.start(validKaInterval);
- callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ notMyNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
+ }
// Invalid interval.
- ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
- ka.start(invalidKaInterval);
- callback.expectError(SocketKeepalive.ERROR_INVALID_INTERVAL);
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
+ ka.start(invalidKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_INTERVAL);
+ }
// Invalid destination.
- ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv6, executor, callback);
- ka.start(validKaInterval);
- callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv4, dstIPv6, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ }
// Invalid source;
- ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv6, dstIPv4, executor, callback);
- ka.start(validKaInterval);
- callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv6, dstIPv4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ }
// NAT-T is only supported for IPv4.
- ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv6, dstIPv6, executor, callback);
- ka.start(validKaInterval);
- callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv6, dstIPv6, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ }
// Sanity check before testing started keepalive.
- ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
- ka.start(validKaInterval);
- callback.expectError(SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_UNSUPPORTED);
+ }
// Check that a started keepalive can be stopped.
mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
- ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
- ka.start(validKaInterval);
- callback.expectStarted();
- mWiFiNetworkAgent.setStopKeepaliveError(SocketKeepalive.SUCCESS);
- ka.stop();
- callback.expectStopped();
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ mWiFiNetworkAgent.setStopKeepaliveError(SocketKeepalive.SUCCESS);
+ ka.stop();
+ callback.expectStopped();
- // Check that keepalive could be restarted.
- ka.start(validKaInterval);
- callback.expectStarted();
- ka.stop();
- callback.expectStopped();
+ // Check that keepalive could be restarted.
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ ka.stop();
+ callback.expectStopped();
- // Check that keepalive can be restarted without waiting for callback.
- ka.start(validKaInterval);
- callback.expectStarted();
- ka.stop();
- ka.start(validKaInterval);
- callback.expectStopped();
- callback.expectStarted();
- ka.stop();
- callback.expectStopped();
+ // Check that keepalive can be restarted without waiting for callback.
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ ka.stop();
+ ka.start(validKaInterval);
+ callback.expectStopped();
+ callback.expectStarted();
+ ka.stop();
+ callback.expectStopped();
+ }
// Check that deleting the IP address stops the keepalive.
LinkProperties bogusLp = new LinkProperties(lp);
- ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
- ka.start(validKaInterval);
- callback.expectStarted();
- bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25));
- bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25));
- mWiFiNetworkAgent.sendLinkProperties(bogusLp);
- callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
- mWiFiNetworkAgent.sendLinkProperties(lp);
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25));
+ bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25));
+ mWiFiNetworkAgent.sendLinkProperties(bogusLp);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ mWiFiNetworkAgent.sendLinkProperties(lp);
+ }
// Check that a started keepalive is stopped correctly when the network disconnects.
- ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
- ka.start(validKaInterval);
- callback.expectStarted();
- mWiFiNetworkAgent.disconnect();
- waitFor(mWiFiNetworkAgent.getDisconnectedCV());
- callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ mWiFiNetworkAgent.disconnect();
+ waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+ callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
- // ... and that stopping it after that has no adverse effects.
- waitForIdle();
- final Network myNetAlias = myNet;
- assertNull(mCm.getNetworkCapabilities(myNetAlias));
- ka.stop();
- callback.assertNoCallback();
+ // ... and that stopping it after that has no adverse effects.
+ waitForIdle();
+ final Network myNetAlias = myNet;
+ assertNull(mCm.getNetworkCapabilities(myNetAlias));
+ ka.stop();
+ callback.assertNoCallback();
+ }
// Reconnect.
myNet = connectKeepaliveNetwork(lp);
@@ -4155,27 +4179,30 @@
// Check that keepalive slots start from 1 and increment. The first one gets slot 1.
mWiFiNetworkAgent.setExpectedKeepaliveSlot(1);
- ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
- ka.start(validKaInterval);
- callback.expectStarted();
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectStarted();
- // The second one gets slot 2.
- mWiFiNetworkAgent.setExpectedKeepaliveSlot(2);
- final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(6789);
- TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback(executor);
- SocketKeepalive ka2 =
- mCm.createSocketKeepalive(myNet, testSocket2, myIPv4, dstIPv4, executor, callback2);
- ka2.start(validKaInterval);
- callback2.expectStarted();
+ // The second one gets slot 2.
+ mWiFiNetworkAgent.setExpectedKeepaliveSlot(2);
+ final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(6789);
+ TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback(executor);
+ try (SocketKeepalive ka2 = mCm.createSocketKeepalive(
+ myNet, testSocket2, myIPv4, dstIPv4, executor, callback2)) {
+ ka2.start(validKaInterval);
+ callback2.expectStarted();
- ka.stop();
- callback.expectStopped();
+ ka.stop();
+ callback.expectStopped();
- ka2.stop();
- callback2.expectStopped();
+ ka2.stop();
+ callback2.expectStopped();
- testSocket.close();
- testSocket2.close();
+ testSocket.close();
+ testSocket2.close();
+ }
+ }
mWiFiNetworkAgent.disconnect();
waitFor(mWiFiNetworkAgent.getDisconnectedCV());
@@ -4200,7 +4227,6 @@
final InetAddress myIPv6 = InetAddress.getByName("::1");
final int validKaInterval = 15;
- final int invalidKaInterval = 9;
final LinkProperties lp = new LinkProperties();
lp.setInterfaceName("wlan12");
@@ -4216,37 +4242,46 @@
final Socket testSocketV6 = new Socket();
TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(executor);
- SocketKeepalive ka;
// Attempt to start Tcp keepalives with invalid parameters and check for errors.
// Invalid network.
- ka = mCm.createSocketKeepalive(notMyNet, testSocketV4, executor, callback);
- ka.start(validKaInterval);
- callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ notMyNet, testSocketV4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
+ }
// Invalid Socket (socket is not bound with IPv4 address).
- ka = mCm.createSocketKeepalive(myNet, testSocketV4, executor, callback);
- ka.start(validKaInterval);
- callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET);
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocketV4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET);
+ }
// Invalid Socket (socket is not bound with IPv6 address).
- ka = mCm.createSocketKeepalive(myNet, testSocketV6, executor, callback);
- ka.start(validKaInterval);
- callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET);
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocketV6, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET);
+ }
// Bind the socket address
testSocketV4.bind(new InetSocketAddress(myIPv4, srcPortV4));
testSocketV6.bind(new InetSocketAddress(myIPv6, srcPortV6));
// Invalid Socket (socket is bound with IPv4 address).
- ka = mCm.createSocketKeepalive(myNet, testSocketV4, executor, callback);
- ka.start(validKaInterval);
- callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET);
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocketV4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET);
+ }
// Invalid Socket (socket is bound with IPv6 address).
- ka = mCm.createSocketKeepalive(myNet, testSocketV6, executor, callback);
- ka.start(validKaInterval);
- callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET);
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocketV6, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET);
+ }
testSocketV4.close();
testSocketV6.close();
@@ -4256,6 +4291,66 @@
mWiFiNetworkAgent = null;
}
+ private void doTestNattSocketKeepalivesFdWithExecutor(Executor executor) throws Exception {
+ final int srcPort = 12345;
+ final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
+ final InetAddress anyIPv4 = InetAddress.getByName("0.0.0.0");
+ final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8");
+ final int validKaInterval = 15;
+
+ // Prepare the target network.
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("wlan12");
+ lp.addLinkAddress(new LinkAddress(myIPv4, 25));
+ lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
+ Network myNet = connectKeepaliveNetwork(lp);
+ mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+ mWiFiNetworkAgent.setStopKeepaliveError(SocketKeepalive.SUCCESS);
+
+ TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(executor);
+
+ // Prepare the target file descriptor, keep only one instance.
+ final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
+ final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort);
+ final ParcelFileDescriptor testPfd =
+ ParcelFileDescriptor.dup(testSocket.getFileDescriptor());
+ testSocket.close();
+ assertTrue(isUdpPortInUse(srcPort));
+
+ // Start keepalive and explicit make the variable goes out of scope with try-with-resources
+ // block.
+ try (SocketKeepalive ka = mCm.createNattKeepalive(
+ myNet, testPfd, myIPv4, dstIPv4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ ka.stop();
+ callback.expectStopped();
+ }
+
+ // Check that the ParcelFileDescriptor is still valid after keepalive stopped,
+ // ErrnoException with EBADF will be thrown if the socket is closed when checking local
+ // address.
+ assertTrue(isUdpPortInUse(srcPort));
+ final InetSocketAddress sa =
+ (InetSocketAddress) Os.getsockname(testPfd.getFileDescriptor());
+ assertEquals(anyIPv4, sa.getAddress());
+
+ testPfd.close();
+ assertFalse(isUdpPortInUse(srcPort));
+
+ mWiFiNetworkAgent.disconnect();
+ waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+ mWiFiNetworkAgent = null;
+ }
+
+ private static boolean isUdpPortInUse(int port) {
+ try (DatagramSocket ignored = new DatagramSocket(port)) {
+ return false;
+ } catch (IOException ignored) {
+ return true;
+ }
+ }
+
@Test
public void testGetCaptivePortalServerUrl() throws Exception {
String url = mCm.getCaptivePortalServerUrl();