Merge changes from topic "ka05"

* changes:
  [KA05] Export keepalive offload api for IpSec Nat-T file descriptor
  [KA01] export SocketKeepalive API for NAT-T keepalive
This commit is contained in:
Junyu Lai
2019-01-24 05:06:39 +00:00
committed by Gerrit Code Review
7 changed files with 692 additions and 21 deletions

View File

@@ -15,6 +15,9 @@
*/
package android.net;
import static android.net.IpSecManager.INVALID_RESOURCE_ID;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,6 +31,8 @@ import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.IpSecManager.UdpEncapsulationSocket;
import android.net.SocketKeepalive.Callback;
import android.os.Binder;
import android.os.Build;
import android.os.Build.VERSION_CODES;
@@ -58,6 +63,7 @@ import com.android.internal.util.Protocol;
import libcore.net.event.NetworkEventDispatcher;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.InetAddress;
@@ -66,6 +72,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
/**
* Class that answers queries about the state of network connectivity. It also
@@ -1699,6 +1706,8 @@ public class ConnectivityManager {
* {@link PacketKeepaliveCallback#onStopped} if the operation was successful or
* {@link PacketKeepaliveCallback#onError} if an error occurred.
*
* @deprecated Use {@link SocketKeepalive} instead.
*
* @hide
*/
public class PacketKeepalive {
@@ -1802,6 +1811,8 @@ public class ConnectivityManager {
/**
* Starts an IPsec NAT-T keepalive packet with the specified parameters.
*
* @deprecated Use {@link #createSocketKeepalive} instead.
*
* @hide
*/
@UnsupportedAppUsage
@@ -1820,6 +1831,62 @@ public class ConnectivityManager {
return k;
}
/**
* Request that keepalives be started on a IPsec NAT-T socket.
*
* @param network The {@link Network} the socket is on.
* @param socket The socket that needs to be kept alive.
* @param source The source address of the {@link UdpEncapsulationSocket}.
* @param destination The destination address of the {@link UdpEncapsulationSocket}.
* @param executor The executor on which callback will be invoked. The provided {@link Executor}
* must run callback sequentially, otherwise the order of callbacks cannot be
* guaranteed.
* @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
* changes. Must be extended by applications that use this API.
*
* @return A {@link SocketKeepalive} object, which can be used to control this keepalive object.
**/
public 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);
}
/**
* Request that keepalives be started on a IPsec NAT-T socket file descriptor. Directly called
* 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 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}.
* @param executor The executor on which callback will be invoked. The provided {@link Executor}
* must run callback sequentially, otherwise the order of callbacks cannot be
* guaranteed.
* @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
* changes. Must be extended by applications that use this API.
*
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD)
public SocketKeepalive createNattKeepalive(@NonNull Network network,
@NonNull FileDescriptor fd,
@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);
}
/**
* Ensure that a network route exists to deliver traffic to the specified
* host via the specified network interface. An attempt to add a route that

View File

@@ -181,6 +181,10 @@ interface IConnectivityManager
void startNattKeepalive(in Network network, int intervalSeconds, in Messenger messenger,
in IBinder binder, String srcAddr, int srcPort, String dstAddr);
void startNattKeepaliveWithFd(in Network network, in FileDescriptor fd, int resourceId,
int intervalSeconds, in Messenger messenger, in IBinder binder, String srcAddr,
String dstAddr);
void stopKeepalive(in Network network, int slot);
String getCaptivePortalServerUrl();

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net;
import android.annotation.NonNull;
import android.os.Binder;
import android.os.RemoteException;
import android.util.Log;
import java.io.FileDescriptor;
import java.net.InetAddress;
import java.util.concurrent.Executor;
/** @hide */
public final class NattSocketKeepalive extends SocketKeepalive {
/** The NAT-T destination port for IPsec */
public static final int NATT_PORT = 4500;
@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,
int resourceId,
@NonNull InetAddress source,
@NonNull InetAddress destination,
@NonNull Executor executor,
@NonNull Callback callback) {
super(service, network, executor, callback);
mSource = source;
mDestination = destination;
mFd = fd;
mResourceId = resourceId;
}
@Override
void startImpl(int intervalSec) {
try {
mService.startNattKeepaliveWithFd(mNetwork, mFd, mResourceId, intervalSec, mMessenger,
new Binder(), mSource.getHostAddress(), mDestination.getHostAddress());
} catch (RemoteException e) {
Log.e(TAG, "Error starting packet keepalive: ", e);
stopLooper();
}
}
@Override
void stopImpl() {
try {
if (mSlot != null) {
mService.stopKeepalive(mNetwork, mSlot);
}
} catch (RemoteException e) {
Log.e(TAG, "Error stopping packet keepalive: ", e);
stopLooper();
}
}
}

View File

@@ -0,0 +1,224 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.Process;
import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;
/**
* Allows applications to request that the system periodically send specific packets on their
* behalf, using hardware offload to save battery power.
*
* To request that the system send keepalives, call one of the methods that return a
* {@link SocketKeepalive} object, such as {@link ConnectivityManager#createSocketKeepalive},
* passing in a non-null callback. If the {@link SocketKeepalive} is successfully
* started, the callback's {@code onStarted} method will be called. If an error occurs,
* {@code onError} will be called, specifying one of the {@code ERROR_*} constants in this
* class.
*
* To stop an existing keepalive, call {@link SocketKeepalive#stop}. The system will call
* {@link SocketKeepalive.Callback#onStopped} if the operation was successful or
* {@link SocketKeepalive.Callback#onError} if an error occurred.
*/
public abstract class SocketKeepalive implements AutoCloseable {
static final String TAG = "SocketKeepalive";
/** @hide */
public static final int SUCCESS = 0;
/** @hide */
public static final int NO_KEEPALIVE = -1;
/** @hide */
public static final int DATA_RECEIVED = -2;
/** @hide */
public static final int BINDER_DIED = -10;
/** The specified {@code Network} is not connected. */
public static final int ERROR_INVALID_NETWORK = -20;
/** The specified IP addresses are invalid. For example, the specified source IP address is
* not configured on the specified {@code Network}. */
public static final int ERROR_INVALID_IP_ADDRESS = -21;
/** The requested port is invalid. */
public static final int ERROR_INVALID_PORT = -22;
/** The packet length is invalid (e.g., too long). */
public static final int ERROR_INVALID_LENGTH = -23;
/** The packet transmission interval is invalid (e.g., too short). */
public static final int ERROR_INVALID_INTERVAL = -24;
/** The target socket is invalid. */
public static final int ERROR_INVALID_SOCKET = -25;
/** 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 hardware returned an error. */
public static final int ERROR_HARDWARE_ERROR = -31;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "ERROR_" }, value = {
ERROR_INVALID_NETWORK,
ERROR_INVALID_IP_ADDRESS,
ERROR_INVALID_PORT,
ERROR_INVALID_LENGTH,
ERROR_INVALID_INTERVAL,
ERROR_INVALID_SOCKET,
ERROR_SOCKET_NOT_IDLE
})
public @interface ErrorCode {}
/**
* The minimum interval in seconds between keepalive packet transmissions.
*
* @hide
**/
public static final int MIN_INTERVAL_SEC = 10;
/**
* The maximum interval in seconds between keepalive packet transmissions.
*
* @hide
**/
public static final int MAX_INTERVAL_SEC = 3600;
@NonNull final IConnectivityManager mService;
@NonNull final Network mNetwork;
@NonNull private final Executor mExecutor;
@NonNull private final SocketKeepalive.Callback mCallback;
@NonNull private final Looper mLooper;
@NonNull final Messenger mMessenger;
@NonNull Integer mSlot;
SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network,
@NonNull Executor executor, @NonNull Callback callback) {
mService = service;
mNetwork = network;
mExecutor = executor;
mCallback = callback;
// TODO: 1. Use other thread modeling instead of create one thread for every instance to
// reduce the memory cost.
// 2. support restart.
// 3. Fix race condition which caused by rapidly start and stop.
HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND
+ Process.THREAD_PRIORITY_LESS_FAVORABLE);
thread.start();
mLooper = thread.getLooper();
mMessenger = new Messenger(new Handler(mLooper) {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case NetworkAgent.EVENT_PACKET_KEEPALIVE:
final int status = message.arg2;
try {
if (status == SUCCESS) {
if (mSlot == null) {
mSlot = message.arg1;
mExecutor.execute(() -> mCallback.onStarted());
} else {
mSlot = null;
stopLooper();
mExecutor.execute(() -> mCallback.onStopped());
}
} else if (status == DATA_RECEIVED) {
stopLooper();
mExecutor.execute(() -> mCallback.onDataReceived());
} else {
stopLooper();
mExecutor.execute(() -> mCallback.onError(status));
}
} catch (Exception e) {
Log.e(TAG, "Exception in keepalive callback(" + status + ")", e);
}
break;
default:
Log.e(TAG, "Unhandled message " + Integer.toHexString(message.what));
break;
}
}
});
}
/**
* Request that keepalive be started with the given {@code intervalSec}. See
* {@link SocketKeepalive}.
*
* @param intervalSec The target interval in seconds between keepalive packet transmissions.
* The interval should be between 10 seconds and 3600 seconds, otherwise
* {@link #ERROR_INVALID_INTERVAL} will be returned.
*/
public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC)
int intervalSec) {
startImpl(intervalSec);
}
abstract void startImpl(int intervalSec);
/** @hide */
protected void stopLooper() {
// TODO: remove this after changing thread modeling.
mLooper.quit();
}
/**
* Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped}
* before using the object. See {@link SocketKeepalive}.
*/
public final void stop() {
stopImpl();
}
abstract void stopImpl();
/**
* Deactivate this {@link SocketKeepalive} and free allocated resources. The instance won't be
* usable again if {@code close()} is called.
*/
@Override
public final void close() {
stop();
stopLooper();
}
/**
* The callback which app can use to learn the status changes of {@link SocketKeepalive}. See
* {@link SocketKeepalive}.
*/
public static class Callback {
/** The requested keepalive was successfully started. */
public void onStarted() {}
/** The keepalive was successfully stopped. */
public void onStopped() {}
/** An error occurred. */
public void onError(@ErrorCode int error) {}
/** The keepalive on a TCP socket was stopped because the socket received data. */
public void onDataReceived() {}
}
}

View File

@@ -73,6 +73,7 @@ import android.net.INetworkStatsService;
import android.net.LinkProperties;
import android.net.LinkProperties.CompareResult;
import android.net.MatchAllNetworkSpecifier;
import android.net.NattSocketKeepalive;
import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
@@ -6184,6 +6185,17 @@ public class ConnectivityService extends IConnectivityManager.Stub
srcAddr, srcPort, dstAddr, ConnectivityManager.PacketKeepalive.NATT_PORT);
}
@Override
public void startNattKeepaliveWithFd(Network network, FileDescriptor fd, int resourceId,
int intervalSeconds, Messenger messenger, IBinder binder, String srcAddr,
String dstAddr) {
enforceKeepalivePermission();
mKeepaliveTracker.startNattKeepalive(
getNetworkAgentInfoForNetwork(network), fd, resourceId,
intervalSeconds, messenger, binder,
srcAddr, dstAddr, NattSocketKeepalive.NATT_PORT);
}
@Override
public void stopKeepalive(Network network, int slot) {
mHandler.sendMessage(mHandler.obtainMessage(

View File

@@ -16,40 +16,49 @@
package com.android.server.connectivity;
import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.connectivity.NetworkAgentInfo;
import android.net.ConnectivityManager;
// TODO: Clean up imports and remove references of PacketKeepalive constants.
import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_INTERVAL;
import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_IP_ADDRESS;
import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK;
import static android.net.ConnectivityManager.PacketKeepalive.MIN_INTERVAL;
import static android.net.ConnectivityManager.PacketKeepalive.NATT_PORT;
import static android.net.ConnectivityManager.PacketKeepalive.NO_KEEPALIVE;
import static android.net.ConnectivityManager.PacketKeepalive.SUCCESS;
import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE;
import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE;
import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE;
import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.ConnectivityManager.PacketKeepalive;
import android.net.KeepalivePacketData;
import android.net.LinkAddress;
import android.net.NetworkAgent;
import android.net.NetworkUtils;
import android.net.util.IpUtils;
import android.os.Binder;
import android.os.IBinder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.Process;
import android.os.RemoteException;
import android.system.OsConstants;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
import android.util.Pair;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.net.Inet4Address;
import java.net.Inet6Address;
import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;
import java.io.FileDescriptor;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import static android.net.ConnectivityManager.PacketKeepalive.*;
import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE;
import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE;
import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE;
/**
* Manages packet keepalive requests.
*
@@ -300,7 +309,9 @@ public class KeepaliveTracker {
}
}
public void handleEventPacketKeepalive(NetworkAgentInfo nai, Message message) {
/** Handle keepalive events from lower layer. */
public void handleEventPacketKeepalive(@NonNull NetworkAgentInfo nai,
@NonNull Message message) {
int slot = message.arg1;
int reason = message.arg2;
@@ -330,8 +341,18 @@ public class KeepaliveTracker {
}
}
public void startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger,
IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort) {
/**
* Called when requesting that keepalives be started on a IPsec NAT-T socket. See
* {@link android.net.SocketKeepalive}.
**/
public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
int intervalSeconds,
@NonNull Messenger messenger,
@NonNull IBinder binder,
@NonNull String srcAddrString,
int srcPort,
@NonNull String dstAddrString,
int dstPort) {
if (nai == null) {
notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK);
return;
@@ -360,6 +381,56 @@ public class KeepaliveTracker {
NetworkAgent.CMD_START_PACKET_KEEPALIVE, ki).sendToTarget();
}
/**
* 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,
@Nullable FileDescriptor fd,
int resourceId,
int intervalSeconds,
@NonNull Messenger messenger,
@NonNull IBinder binder,
@NonNull String srcAddrString,
@NonNull String dstAddrString,
int dstPort) {
// Ensure that the socket is created by IpSecService.
if (!isNattKeepaliveSocketValid(fd, resourceId)) {
notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_SOCKET);
}
// Get src port to adopt old API.
int srcPort = 0;
try {
final SocketAddress srcSockAddr = Os.getsockname(fd);
srcPort = ((InetSocketAddress) srcSockAddr).getPort();
} catch (ErrnoException e) {
notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_SOCKET);
}
// Forward request to old API.
startNattKeepalive(nai, intervalSeconds, messenger, binder, srcAddrString, srcPort,
dstAddrString, dstPort);
}
/**
* Verify if the IPsec NAT-T file descriptor and resource Id hold for IPsec keepalive is valid.
**/
public static boolean isNattKeepaliveSocketValid(@Nullable FileDescriptor fd, int resourceId) {
// TODO: 1. confirm whether the fd is called from system api or created by IpSecService.
// 2. If the fd is created from the system api, check that it's bounded. And
// call dup to keep the fd open.
// 3. If the fd is created from IpSecService, check if the resource ID is valid. And
// hold the resource needed in IpSecService.
if (null == fd) {
return false;
}
return true;
}
public void dump(IndentingPrintWriter pw) {
pw.println("Packet keepalives:");
pw.increaseIndent();

View File

@@ -107,6 +107,8 @@ import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.net.InterfaceConfiguration;
import android.net.IpPrefix;
import android.net.IpSecManager;
import android.net.IpSecManager.UdpEncapsulationSocket;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MatchAllNetworkSpecifier;
@@ -122,6 +124,7 @@ import android.net.NetworkSpecifier;
import android.net.NetworkStack;
import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.net.SocketKeepalive;
import android.net.UidRange;
import android.net.metrics.IpConnectivityLog;
import android.net.shared.NetworkMonitorUtils;
@@ -186,6 +189,8 @@ import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -402,8 +407,8 @@ public class ConnectivityServiceTest {
private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
private int mScore;
private NetworkAgent mNetworkAgent;
private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
private int mStopKeepaliveError = PacketKeepalive.NO_KEEPALIVE;
private int mStartKeepaliveError = SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE;
private Integer mExpectedKeepaliveSlot = null;
// Contains the redirectUrl from networkStatus(). Before reading, wait for
// mNetworkStatusReceived.
@@ -3548,6 +3553,80 @@ public class ConnectivityServiceTest {
}
}
private static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback {
public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
private class CallbackValue {
public CallbackType callbackType;
public int error;
CallbackValue(CallbackType type) {
this.callbackType = type;
this.error = SocketKeepalive.SUCCESS;
assertTrue("onError callback must have error", type != CallbackType.ON_ERROR);
}
CallbackValue(CallbackType type, int error) {
this.callbackType = type;
this.error = error;
assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR);
}
@Override
public boolean equals(Object o) {
return o instanceof CallbackValue
&& this.callbackType == ((CallbackValue) o).callbackType
&& this.error == ((CallbackValue) o).error;
}
@Override
public String toString() {
return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType,
error);
}
}
private LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>();
@Override
public void onStarted() {
mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED));
}
@Override
public void onStopped() {
mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED));
}
@Override
public void onError(int error) {
mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error));
}
private void expectCallback(CallbackValue callbackValue) {
try {
assertEquals(
callbackValue,
mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} catch (InterruptedException e) {
fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms");
}
}
public void expectStarted() {
expectCallback(new CallbackValue(CallbackType.ON_STARTED));
}
public void expectStopped() {
expectCallback(new CallbackValue(CallbackType.ON_STOPPED));
}
public void expectError(int error) {
expectCallback(new CallbackValue(CallbackType.ON_ERROR, error));
}
}
private Network connectKeepaliveNetwork(LinkProperties lp) {
// Ensure the network is disconnected before we do anything.
if (mWiFiNetworkAgent != null) {
@@ -3694,6 +3773,145 @@ public class ConnectivityServiceTest {
callback3.expectStopped();
}
@Test
public void testNattSocketKeepalives() throws Exception {
// TODO: 1. Move this outside of ConnectivityServiceTest.
// 2. Add helper function to test against newSingleThreadExecutor as well as inline
// executor.
// 3. Make test to verify that Nat-T keepalive socket is created by IpSecService.
final int srcPort = 12345;
final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
final InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35");
final InetAddress myIPv6 = InetAddress.getByName("2001:db8::1");
final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8");
final InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888");
final int validKaInterval = 15;
final int invalidKaInterval = 9;
final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort);
final Executor executor = Executors.newSingleThreadExecutor();
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("wlan12");
lp.addLinkAddress(new LinkAddress(myIPv6, 64));
lp.addLinkAddress(new LinkAddress(myIPv4, 25));
lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234")));
lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
Network notMyNet = new Network(61234);
Network myNet = connectKeepaliveNetwork(lp);
TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
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);
// Invalid interval.
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);
// Invalid source;
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);
// Sanity check before testing started keepalive.
ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
ka.start(validKaInterval);
callback.expectError(SocketKeepalive.ERROR_HARDWARE_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();
// 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);
// 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);
// ... and that stopping it after that has no adverse effects.
waitForIdle();
final Network myNetAlias = myNet;
assertNull(mCm.getNetworkCapabilities(myNetAlias));
ka.stop();
// Reconnect.
myNet = connectKeepaliveNetwork(lp);
mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
// Check things work as expected when the keepalive is stopped and the network disconnects.
ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
ka.start(validKaInterval);
callback.expectStarted();
ka.stop();
mWiFiNetworkAgent.disconnect();
waitFor(mWiFiNetworkAgent.getDisconnectedCV());
waitForIdle();
callback.expectStopped();
// Reconnect.
myNet = connectKeepaliveNetwork(lp);
mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
// 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();
// The second one gets slot 2.
mWiFiNetworkAgent.setExpectedKeepaliveSlot(2);
final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(6789);
TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback();
SocketKeepalive ka2 =
mCm.createSocketKeepalive(myNet, testSocket2, myIPv4, dstIPv4, executor, callback2);
ka2.start(validKaInterval);
callback2.expectStarted();
ka.stop();
callback.expectStopped();
ka2.stop();
callback2.expectStopped();
}
@Test
public void testGetCaptivePortalServerUrl() throws Exception {
String url = mCm.getCaptivePortalServerUrl();