Merge "Implement ConnectivityService TODO and fix many race conditions" into oc-mr1-dev
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 1bb0fbb..f527f77 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -18,14 +18,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.net.ProxyInfo;
-import android.os.Parcelable;
import android.os.Parcel;
+import android.os.Parcelable;
import android.text.TextUtils;
-import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.Inet6Address;
+import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
@@ -504,11 +503,22 @@
}
/**
+ * Make sure this LinkProperties instance contains routes that cover the local subnet
+ * of its link addresses. Add any route that is missing.
+ * @hide
+ */
+ public void ensureDirectlyConnectedRoutes() {
+ for (LinkAddress addr: mLinkAddresses) {
+ addRoute(new RouteInfo(addr, null, mIfaceName));
+ }
+ }
+
+ /**
* Returns all the routes on this link and all the links stacked above it.
* @hide
*/
public List<RouteInfo> getAllRoutes() {
- List<RouteInfo> routes = new ArrayList();
+ List<RouteInfo> routes = new ArrayList<>();
routes.addAll(mRoutes);
for (LinkProperties stacked: mStackedLinks.values()) {
routes.addAll(stacked.getAllRoutes());
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index d5f6321..9686dd9 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -24,10 +24,15 @@
import android.system.OsConstants;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.Suppress;
+import android.util.ArraySet;
+
import junit.framework.TestCase;
import java.net.InetAddress;
-import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
public class LinkPropertiesTest extends TestCase {
@@ -678,4 +683,76 @@
stacked.addRoute(new RouteInfo((IpPrefix) null, stackedAddress));
assertTrue(v6lp.isReachable(DNS1));
}
+
+ @SmallTest
+ public void testLinkPropertiesEnsureDirectlyConnectedRoutes() {
+ // IPv4 case: no route added initially
+ LinkProperties rmnet0 = new LinkProperties();
+ rmnet0.setInterfaceName("rmnet0");
+ rmnet0.addLinkAddress(new LinkAddress("10.0.0.2/8"));
+ RouteInfo directRoute0 = new RouteInfo(new IpPrefix("10.0.0.0/8"), null,
+ rmnet0.getInterfaceName());
+
+ // Since no routes is added explicitly, getAllRoutes() should return empty.
+ assertTrue(rmnet0.getAllRoutes().isEmpty());
+ rmnet0.ensureDirectlyConnectedRoutes();
+ // ensureDirectlyConnectedRoutes() should have added the missing local route.
+ assertEqualRoutes(Collections.singletonList(directRoute0), rmnet0.getAllRoutes());
+
+ // IPv4 case: both direct and default routes added initially
+ LinkProperties rmnet1 = new LinkProperties();
+ rmnet1.setInterfaceName("rmnet1");
+ rmnet1.addLinkAddress(new LinkAddress("10.0.0.3/8"));
+ RouteInfo defaultRoute1 = new RouteInfo((IpPrefix) null,
+ NetworkUtils.numericToInetAddress("10.0.0.1"), rmnet1.getInterfaceName());
+ RouteInfo directRoute1 = new RouteInfo(new IpPrefix("10.0.0.0/8"), null,
+ rmnet1.getInterfaceName());
+ rmnet1.addRoute(defaultRoute1);
+ rmnet1.addRoute(directRoute1);
+
+ // Check added routes
+ assertEqualRoutes(Arrays.asList(defaultRoute1, directRoute1), rmnet1.getAllRoutes());
+ // ensureDirectlyConnectedRoutes() shouldn't change the routes since direct connected
+ // route is already part of the configuration.
+ rmnet1.ensureDirectlyConnectedRoutes();
+ assertEqualRoutes(Arrays.asList(defaultRoute1, directRoute1), rmnet1.getAllRoutes());
+
+ // IPv6 case: only default routes added initially
+ LinkProperties rmnet2 = new LinkProperties();
+ rmnet2.setInterfaceName("rmnet2");
+ rmnet2.addLinkAddress(new LinkAddress("fe80::cafe/64"));
+ rmnet2.addLinkAddress(new LinkAddress("2001:db8::2/64"));
+ RouteInfo defaultRoute2 = new RouteInfo((IpPrefix) null,
+ NetworkUtils.numericToInetAddress("2001:db8::1"), rmnet2.getInterfaceName());
+ RouteInfo directRoute2 = new RouteInfo(new IpPrefix("2001:db8::/64"), null,
+ rmnet2.getInterfaceName());
+ RouteInfo linkLocalRoute2 = new RouteInfo(new IpPrefix("fe80::/64"), null,
+ rmnet2.getInterfaceName());
+ rmnet2.addRoute(defaultRoute2);
+
+ assertEqualRoutes(Arrays.asList(defaultRoute2), rmnet2.getAllRoutes());
+ rmnet2.ensureDirectlyConnectedRoutes();
+ assertEqualRoutes(Arrays.asList(defaultRoute2, directRoute2, linkLocalRoute2),
+ rmnet2.getAllRoutes());
+
+ // Corner case: no interface name
+ LinkProperties rmnet3 = new LinkProperties();
+ rmnet3.addLinkAddress(new LinkAddress("192.168.0.2/24"));
+ RouteInfo directRoute3 = new RouteInfo(new IpPrefix("192.168.0.0/24"), null,
+ rmnet3.getInterfaceName());
+
+ assertTrue(rmnet3.getAllRoutes().isEmpty());
+ rmnet3.ensureDirectlyConnectedRoutes();
+ assertEqualRoutes(Collections.singletonList(directRoute3), rmnet3.getAllRoutes());
+
+ }
+
+ private void assertEqualRoutes(Collection<RouteInfo> expected, Collection<RouteInfo> actual) {
+ Set<RouteInfo> expectedSet = new ArraySet<>(expected);
+ Set<RouteInfo> actualSet = new ArraySet<>(actual);
+ // Duplicated entries in actual routes are considered failures
+ assertEquals(actual.size(), actualSet.size());
+
+ assertEquals(expectedSet, actualSet);
+ }
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 12cdc74..2b4f0f3 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -456,6 +456,11 @@
private static final int MAX_WAKELOCK_LOGS = 20;
private final LocalLog mWakelockLogs = new LocalLog(MAX_WAKELOCK_LOGS);
+ private int mTotalWakelockAcquisitions = 0;
+ private int mTotalWakelockReleases = 0;
+ private long mTotalWakelockDurationMs = 0;
+ private long mMaxWakelockDurationMs = 0;
+ private long mLastWakeLockAcquireTimestamp = 0;
// Array of <Network,ReadOnlyLocalLogs> tracking network validation and results
private static final int MAX_VALIDATION_LOGS = 10;
@@ -1947,6 +1952,14 @@
pw.println();
pw.println("NetTransition WakeLock activity (most recent first):");
pw.increaseIndent();
+ pw.println("total acquisitions: " + mTotalWakelockAcquisitions);
+ pw.println("total releases: " + mTotalWakelockReleases);
+ pw.println("cumulative duration: " + (mTotalWakelockDurationMs / 1000) + "s");
+ pw.println("longest duration: " + (mMaxWakelockDurationMs / 1000) + "s");
+ if (mTotalWakelockAcquisitions > mTotalWakelockReleases) {
+ long duration = SystemClock.elapsedRealtime() - mLastWakeLockAcquireTimestamp;
+ pw.println("currently holding WakeLock for: " + (duration / 1000) + "s");
+ }
mWakelockLogs.reverseDump(fd, pw, args);
pw.decreaseIndent();
}
@@ -2011,16 +2024,7 @@
break;
}
case NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED: {
- if (VDBG) {
- log("Update of LinkProperties for " + nai.name() +
- "; created=" + nai.created +
- "; everConnected=" + nai.everConnected);
- }
- LinkProperties oldLp = nai.linkProperties;
- synchronized (nai) {
- nai.linkProperties = (LinkProperties)msg.obj;
- }
- if (nai.everConnected) updateLinkProperties(nai, oldLp);
+ handleUpdateLinkProperties(nai, (LinkProperties) msg.obj);
break;
}
case NetworkAgent.EVENT_NETWORK_INFO_CHANGED: {
@@ -2269,7 +2273,7 @@
}
nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
mNetworkAgentInfos.remove(msg.replyTo);
- maybeStopClat(nai);
+ nai.maybeStopClat();
synchronized (mNetworkForNetId) {
// Remove the NetworkAgent, but don't mark the netId as
// available until we've told netd to delete it below.
@@ -3014,6 +3018,8 @@
return;
}
mNetTransitionWakeLock.acquire();
+ mLastWakeLockAcquireTimestamp = SystemClock.elapsedRealtime();
+ mTotalWakelockAcquisitions++;
}
mWakelockLogs.log("ACQUIRE for " + forWhom);
Message msg = mHandler.obtainMessage(EVENT_EXPIRE_NET_TRANSITION_WAKELOCK);
@@ -3046,6 +3052,10 @@
return;
}
mNetTransitionWakeLock.release();
+ long lockDuration = SystemClock.elapsedRealtime() - mLastWakeLockAcquireTimestamp;
+ mTotalWakelockDurationMs += lockDuration;
+ mMaxWakelockDurationMs = Math.max(mMaxWakelockDurationMs, lockDuration);
+ mTotalWakelockReleases++;
}
mWakelockLogs.log(String.format("RELEASE (%s)", event));
}
@@ -4352,11 +4362,13 @@
int currentScore, NetworkMisc networkMisc) {
enforceConnectivityInternalPermission();
+ LinkProperties lp = new LinkProperties(linkProperties);
+ lp.ensureDirectlyConnectedRoutes();
// TODO: Instead of passing mDefaultRequest, provide an API to determine whether a Network
// satisfies mDefaultRequest.
final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
- new Network(reserveNetId()), new NetworkInfo(networkInfo), new LinkProperties(
- linkProperties), new NetworkCapabilities(networkCapabilities), currentScore,
+ new Network(reserveNetId()), new NetworkInfo(networkInfo), lp,
+ new NetworkCapabilities(networkCapabilities), currentScore,
mContext, mTrackerHandler, new NetworkMisc(networkMisc), mDefaultRequest, this);
synchronized (this) {
nai.networkMonitor.systemReady = mSystemReady;
@@ -4402,7 +4414,7 @@
updateDnses(newLp, oldLp, netId);
// Start or stop clat accordingly to network state.
- updateClat(networkAgent);
+ networkAgent.updateClat(mNetd);
if (isDefaultNetwork(networkAgent)) {
handleApplyDefaultProxy(newLp.getHttpProxy());
} else {
@@ -4417,32 +4429,6 @@
mKeepaliveTracker.handleCheckKeepalivesStillValid(networkAgent);
}
- private void updateClat(NetworkAgentInfo nai) {
- if (Nat464Xlat.requiresClat(nai)) {
- maybeStartClat(nai);
- } else {
- maybeStopClat(nai);
- }
- }
-
- /** Ensure clat has started for this network. */
- private void maybeStartClat(NetworkAgentInfo nai) {
- if (nai.clatd != null && nai.clatd.isStarted()) {
- return;
- }
- nai.clatd = new Nat464Xlat(mNetd, mTrackerHandler, nai);
- nai.clatd.start();
- }
-
- /** Ensure clat has stopped for this network. */
- private void maybeStopClat(NetworkAgentInfo nai) {
- if (nai.clatd == null) {
- return;
- }
- nai.clatd.stop();
- nai.clatd = null;
- }
-
private void wakeupModifyInterface(String iface, NetworkCapabilities caps, boolean add) {
// Marks are only available on WiFi interaces. Checking for
// marks on unsupported interfaces is harmless.
@@ -4677,6 +4663,27 @@
}
}
+ public void handleUpdateLinkProperties(NetworkAgentInfo nai, LinkProperties newLp) {
+ if (mNetworkForNetId.get(nai.network.netId) != nai) {
+ // Ignore updates for disconnected networks
+ return;
+ }
+ // newLp is already a defensive copy.
+ newLp.ensureDirectlyConnectedRoutes();
+ if (VDBG) {
+ log("Update of LinkProperties for " + nai.name() +
+ "; created=" + nai.created +
+ "; everConnected=" + nai.everConnected);
+ }
+ LinkProperties oldLp = nai.linkProperties;
+ synchronized (nai) {
+ nai.linkProperties = newLp;
+ }
+ if (nai.everConnected) {
+ updateLinkProperties(nai, oldLp);
+ }
+ }
+
private void sendUpdatedScoreToFactories(NetworkAgentInfo nai) {
for (int i = 0; i < nai.numNetworkRequests(); i++) {
NetworkRequest nr = nai.requestAt(i);
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index f8d23d4..e6585ad 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -20,22 +20,21 @@
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
-import android.net.NetworkAgent;
import android.net.RouteInfo;
-import android.os.Handler;
-import android.os.Message;
import android.os.INetworkManagementService;
import android.os.RemoteException;
import android.util.Slog;
-import com.android.server.net.BaseNetworkObserver;
import com.android.internal.util.ArrayUtils;
+import com.android.server.net.BaseNetworkObserver;
import java.net.Inet4Address;
import java.util.Objects;
/**
- * Class to manage a 464xlat CLAT daemon.
+ * Class to manage a 464xlat CLAT daemon. Nat464Xlat is not thread safe and should be manipulated
+ * from a consistent and unique thread context. It is the responsibility of ConnectivityService to
+ * call into this class from its own Handler thread.
*
* @hide
*/
@@ -55,28 +54,23 @@
private final INetworkManagementService mNMService;
- // ConnectivityService Handler for LinkProperties updates.
- private final Handler mHandler;
-
// The network we're running on, and its type.
private final NetworkAgentInfo mNetwork;
private enum State {
IDLE, // start() not called. Base iface and stacked iface names are null.
STARTING, // start() called. Base iface and stacked iface names are known.
- RUNNING; // start() called, and the stacked iface is known to be up.
+ RUNNING, // start() called, and the stacked iface is known to be up.
+ STOPPING; // stop() called, this Nat464Xlat is still registered as a network observer for
+ // the stacked interface.
}
- // Once mIface is non-null and isStarted() is true, methods called by ConnectivityService on
- // its handler thread must not modify any internal state variables; they are only updated by the
- // interface observers, called on the notification threads.
private String mBaseIface;
private String mIface;
- private volatile State mState = State.IDLE;
+ private State mState = State.IDLE;
- public Nat464Xlat(INetworkManagementService nmService, Handler handler, NetworkAgentInfo nai) {
+ public Nat464Xlat(INetworkManagementService nmService, NetworkAgentInfo nai) {
mNMService = nmService;
- mHandler = handler;
mNetwork = nai;
}
@@ -89,6 +83,8 @@
// TODO: migrate to NetworkCapabilities.TRANSPORT_*.
final int netType = nai.networkInfo.getType();
final boolean supported = ArrayUtils.contains(NETWORK_TYPES, nai.networkInfo.getType());
+ // TODO: this should also consider if the network is in SUSPENDED state to avoid stopping
+ // clatd in SUSPENDED state.
final boolean connected = nai.networkInfo.isConnected();
// We only run clat on networks that don't have a native IPv4 address.
final boolean hasIPv4Address =
@@ -105,6 +101,13 @@
}
/**
+ * @return true if clatd has been started but the stacked interface is not yet up.
+ */
+ public boolean isStarting() {
+ return mState == State.STARTING;
+ }
+
+ /**
* @return true if clatd has been started and the stacked interface is up.
*/
public boolean isRunning() {
@@ -112,25 +115,77 @@
}
/**
- * Sets internal state.
+ * @return true if clatd has been stopped.
+ */
+ public boolean isStopping() {
+ return mState == State.STOPPING;
+ }
+
+ /**
+ * Start clatd, register this Nat464Xlat as a network observer for the stacked interface,
+ * and set internal state.
*/
private void enterStartingState(String baseIface) {
+ try {
+ mNMService.registerObserver(this);
+ } catch(RemoteException e) {
+ Slog.e(TAG,
+ "startClat: Can't register interface observer for clat on " + mNetwork.name());
+ return;
+ }
+ try {
+ mNMService.startClatd(baseIface);
+ } catch(RemoteException|IllegalStateException e) {
+ Slog.e(TAG, "Error starting clatd on " + baseIface, e);
+ }
mIface = CLAT_PREFIX + baseIface;
mBaseIface = baseIface;
mState = State.STARTING;
}
/**
- * Clears internal state. Must not be called by ConnectivityService.
+ * Enter running state just after getting confirmation that the stacked interface is up, and
+ * turn ND offload off if on WiFi.
+ */
+ private void enterRunningState() {
+ maybeSetIpv6NdOffload(mBaseIface, false);
+ mState = State.RUNNING;
+ }
+
+ /**
+ * Stop clatd, and turn ND offload on if it had been turned off.
+ */
+ private void enterStoppingState() {
+ if (isRunning()) {
+ maybeSetIpv6NdOffload(mBaseIface, true);
+ }
+
+ try {
+ mNMService.stopClatd(mBaseIface);
+ } catch(RemoteException|IllegalStateException e) {
+ Slog.e(TAG, "Error stopping clatd on " + mBaseIface, e);
+ }
+
+ mState = State.STOPPING;
+ }
+
+ /**
+ * Unregister as a base observer for the stacked interface, and clear internal state.
*/
private void enterIdleState() {
+ try {
+ mNMService.unregisterObserver(this);
+ } catch(RemoteException|IllegalStateException e) {
+ Slog.e(TAG, "Error unregistering clatd observer on " + mBaseIface, e);
+ }
+
mIface = null;
mBaseIface = null;
mState = State.IDLE;
}
/**
- * Starts the clat daemon. Called by ConnectivityService on the handler thread.
+ * Starts the clat daemon.
*/
public void start() {
if (isStarted()) {
@@ -143,53 +198,30 @@
return;
}
- try {
- mNMService.registerObserver(this);
- } catch(RemoteException e) {
- Slog.e(TAG, "startClat: Can't register interface observer for clat on " + mNetwork);
- return;
- }
-
String baseIface = mNetwork.linkProperties.getInterfaceName();
if (baseIface == null) {
Slog.e(TAG, "startClat: Can't start clat on null interface");
return;
}
// TODO: should we only do this if mNMService.startClatd() succeeds?
+ Slog.i(TAG, "Starting clatd on " + baseIface);
enterStartingState(baseIface);
-
- Slog.i(TAG, "Starting clatd on " + mBaseIface);
- try {
- mNMService.startClatd(mBaseIface);
- } catch(RemoteException|IllegalStateException e) {
- Slog.e(TAG, "Error starting clatd on " + mBaseIface, e);
- }
}
/**
- * Stops the clat daemon. Called by ConnectivityService on the handler thread.
+ * Stops the clat daemon.
*/
public void stop() {
if (!isStarted()) {
- Slog.e(TAG, "stopClat: already stopped or not started");
return;
}
-
Slog.i(TAG, "Stopping clatd on " + mBaseIface);
- try {
- mNMService.stopClatd(mBaseIface);
- } catch(RemoteException|IllegalStateException e) {
- Slog.e(TAG, "Error stopping clatd on " + mBaseIface, e);
- }
- // When clatd stops and its interface is deleted, interfaceRemoved() will notify
- // ConnectivityService and call enterIdleState().
- }
- private void updateConnectivityService(LinkProperties lp) {
- Message msg = mHandler.obtainMessage(NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED, lp);
- msg.replyTo = mNetwork.messenger;
- Slog.i(TAG, "sending message to ConnectivityService: " + msg);
- msg.sendToTarget();
+ boolean wasStarting = isStarting();
+ enterStoppingState();
+ if (wasStarting) {
+ enterIdleState();
+ }
}
/**
@@ -257,59 +289,58 @@
}
/**
- * Adds stacked link on base link and transitions to Running state
- * This is called by the InterfaceObserver on its own thread, so can race with stop().
+ * Adds stacked link on base link and transitions to RUNNING state.
*/
- @Override
- public void interfaceLinkStateChanged(String iface, boolean up) {
- if (!isStarted() || !up || !Objects.equals(mIface, iface)) {
+ private void handleInterfaceLinkStateChanged(String iface, boolean up) {
+ if (!isStarting() || !up || !Objects.equals(mIface, iface)) {
return;
}
- if (isRunning()) {
- return;
- }
+
LinkAddress clatAddress = getLinkAddress(iface);
if (clatAddress == null) {
+ Slog.e(TAG, "clatAddress was null for stacked iface " + iface);
return;
}
- mState = State.RUNNING;
+
Slog.i(TAG, String.format("interface %s is up, adding stacked link %s on top of %s",
mIface, mIface, mBaseIface));
-
- maybeSetIpv6NdOffload(mBaseIface, false);
+ enterRunningState();
LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
lp.addStackedLink(makeLinkProperties(clatAddress));
- updateConnectivityService(lp);
+ mNetwork.connService().handleUpdateLinkProperties(mNetwork, lp);
}
- @Override
- public void interfaceRemoved(String iface) {
- if (!isStarted() || !Objects.equals(mIface, iface)) {
+ /**
+ * Removes stacked link on base link and transitions to IDLE state.
+ */
+ private void handleInterfaceRemoved(String iface) {
+ if (!Objects.equals(mIface, iface)) {
return;
}
- if (!isRunning()) {
+ if (!isRunning() && !isStopping()) {
return;
}
Slog.i(TAG, "interface " + iface + " removed");
- // The interface going away likely means clatd has crashed. Ask netd to stop it,
- // because otherwise when we try to start it again on the same base interface netd
- // will complain that it's already started.
- //
- // Note that this method can be called by the interface observer at the same time
- // that ConnectivityService calls stop(). In this case, the second call to
- // stopClatd() will just throw IllegalStateException, which we'll ignore.
- try {
- mNMService.unregisterObserver(this);
- mNMService.stopClatd(mBaseIface);
- } catch (RemoteException|IllegalStateException e) {
- // Well, we tried.
+ if (!isStopping()) {
+ // Ensure clatd is stopped if stop() has not been called: this likely means that clatd
+ // has crashed.
+ enterStoppingState();
}
- maybeSetIpv6NdOffload(mBaseIface, true);
- LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
- lp.removeStackedLink(mIface);
enterIdleState();
- updateConnectivityService(lp);
+ LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
+ lp.removeStackedLink(iface);
+ mNetwork.connService().handleUpdateLinkProperties(mNetwork, lp);
+ }
+
+ @Override
+ public void interfaceLinkStateChanged(String iface, boolean up) {
+ mNetwork.handler().post(() -> { handleInterfaceLinkStateChanged(iface, up); });
+ }
+
+ @Override
+ public void interfaceRemoved(String iface) {
+ mNetwork.handler().post(() -> { handleInterfaceRemoved(iface); });
}
@Override
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 872923a..e96f4b0 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -27,7 +27,9 @@
import android.net.NetworkRequest;
import android.net.NetworkState;
import android.os.Handler;
+import android.os.INetworkManagementService;
import android.os.Messenger;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
@@ -268,6 +270,14 @@
networkMisc = misc;
}
+ public ConnectivityService connService() {
+ return mConnService;
+ }
+
+ public Handler handler() {
+ return mHandler;
+ }
+
// Functions for manipulating the requests satisfied by this network.
//
// These functions must only called on ConnectivityService's main thread.
@@ -551,6 +561,32 @@
for (LingerTimer timer : mLingerTimers) { pw.println(timer); }
}
+ public void updateClat(INetworkManagementService netd) {
+ if (Nat464Xlat.requiresClat(this)) {
+ maybeStartClat(netd);
+ } else {
+ maybeStopClat();
+ }
+ }
+
+ /** Ensure clat has started for this network. */
+ public void maybeStartClat(INetworkManagementService netd) {
+ if (clatd != null && clatd.isStarted()) {
+ return;
+ }
+ clatd = new Nat464Xlat(netd, this);
+ clatd.start();
+ }
+
+ /** Ensure clat has stopped for this network. */
+ public void maybeStopClat() {
+ if (clatd == null) {
+ return;
+ }
+ clatd.stop();
+ clatd = null;
+ }
+
public String toString() {
return "NetworkAgentInfo{ ni{" + networkInfo + "} " +
"network{" + network + "} nethandle{" + network.getNetworkHandle() + "} " +
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index f6481cf..8816d43 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -64,6 +64,7 @@
import android.net.NetworkMisc;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
+import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.net.StringNetworkSpecifier;
import android.net.metrics.IpConnectivityLog;
@@ -88,6 +89,7 @@
import android.test.mock.MockContentResolver;
import android.test.suitebuilder.annotation.SmallTest;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
import android.util.LogPrinter;
@@ -109,7 +111,10 @@
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -304,6 +309,10 @@
private String mRedirectUrl;
MockNetworkAgent(int transport) {
+ this(transport, new LinkProperties());
+ }
+
+ MockNetworkAgent(int transport, LinkProperties linkProperties) {
final int type = transportToLegacyType(transport);
final String typeName = ConnectivityManager.getNetworkTypeName(type);
mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock");
@@ -329,7 +338,7 @@
mHandlerThread.start();
mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext,
"Mock-" + typeName, mNetworkInfo, mNetworkCapabilities,
- new LinkProperties(), mScore, new NetworkMisc()) {
+ linkProperties, mScore, new NetworkMisc()) {
@Override
public void unwanted() { mDisconnected.open(); }
@@ -3338,6 +3347,68 @@
assertException(() -> { mCm.requestRouteToHostAddress(TYPE_NONE, null); }, unsupported);
}
+ @SmallTest
+ public void testLinkPropertiesEnsuresDirectlyConnectedRoutes() {
+ final NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI).build();
+ final TestNetworkCallback networkCallback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(networkRequest, networkCallback);
+
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("wlan0");
+ LinkAddress myIpv4Address = new LinkAddress("192.168.12.3/24");
+ RouteInfo myIpv4DefaultRoute = new RouteInfo((IpPrefix) null,
+ NetworkUtils.numericToInetAddress("192.168.12.1"), lp.getInterfaceName());
+ lp.addLinkAddress(myIpv4Address);
+ lp.addRoute(myIpv4DefaultRoute);
+
+ // Verify direct routes are added when network agent is first registered in
+ // ConnectivityService.
+ MockNetworkAgent networkAgent = new MockNetworkAgent(TRANSPORT_WIFI, lp);
+ networkAgent.connect(true);
+ networkCallback.expectCallback(CallbackState.AVAILABLE, networkAgent);
+ networkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, networkAgent);
+ CallbackInfo cbi = networkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+ networkAgent);
+ networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, networkAgent);
+ networkCallback.assertNoCallback();
+ checkDirectlyConnectedRoutes(cbi.arg, Arrays.asList(myIpv4Address),
+ Arrays.asList(myIpv4DefaultRoute));
+ checkDirectlyConnectedRoutes(mCm.getLinkProperties(networkAgent.getNetwork()),
+ Arrays.asList(myIpv4Address), Arrays.asList(myIpv4DefaultRoute));
+
+ // Verify direct routes are added during subsequent link properties updates.
+ LinkProperties newLp = new LinkProperties(lp);
+ LinkAddress myIpv6Address1 = new LinkAddress("fe80::cafe/64");
+ LinkAddress myIpv6Address2 = new LinkAddress("2001:db8::2/64");
+ newLp.addLinkAddress(myIpv6Address1);
+ newLp.addLinkAddress(myIpv6Address2);
+ networkAgent.sendLinkProperties(newLp);
+ cbi = networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, networkAgent);
+ networkCallback.assertNoCallback();
+ checkDirectlyConnectedRoutes(cbi.arg,
+ Arrays.asList(myIpv4Address, myIpv6Address1, myIpv6Address2),
+ Arrays.asList(myIpv4DefaultRoute));
+ mCm.unregisterNetworkCallback(networkCallback);
+ }
+
+ private void checkDirectlyConnectedRoutes(Object callbackObj,
+ Collection<LinkAddress> linkAddresses, Collection<RouteInfo> otherRoutes) {
+ assertTrue(callbackObj instanceof LinkProperties);
+ LinkProperties lp = (LinkProperties) callbackObj;
+
+ Set<RouteInfo> expectedRoutes = new ArraySet<>();
+ expectedRoutes.addAll(otherRoutes);
+ for (LinkAddress address : linkAddresses) {
+ RouteInfo localRoute = new RouteInfo(address, null, lp.getInterfaceName());
+ // Duplicates in linkAddresses are considered failures
+ assertTrue(expectedRoutes.add(localRoute));
+ }
+ List<RouteInfo> observedRoutes = lp.getRoutes();
+ assertEquals(expectedRoutes.size(), observedRoutes.size());
+ assertTrue(observedRoutes.containsAll(expectedRoutes));
+ }
+
private static <T> void assertEmpty(T[] ts) {
int length = ts.length;
assertEquals("expected empty array, but length was " + length, 0, length);
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index eff04ab..2624176 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -16,6 +16,8 @@
package com.android.server.connectivity;
+import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
+import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
import static com.android.server.connectivity.MetricsTestUtil.aBool;
import static com.android.server.connectivity.MetricsTestUtil.aByteArray;
import static com.android.server.connectivity.MetricsTestUtil.aLong;
@@ -31,29 +33,41 @@
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.ETHERNET;
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.MULTIPLE;
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.WIFI;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
import android.net.ConnectivityMetricsEvent;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
+import android.net.metrics.ConnectStats;
import android.net.metrics.DefaultNetworkEvent;
import android.net.metrics.DhcpClientEvent;
import android.net.metrics.DhcpErrorEvent;
import android.net.metrics.DnsEvent;
+import android.net.metrics.DnsEvent;
import android.net.metrics.IpManagerEvent;
import android.net.metrics.IpReachabilityEvent;
import android.net.metrics.NetworkEvent;
import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent;
+import android.net.metrics.WakeupStats;
+import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+
import java.util.Arrays;
import java.util.List;
-import junit.framework.TestCase;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
// TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
-public class IpConnectivityEventBuilderTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpConnectivityEventBuilderTest {
- @SmallTest
+ @Test
public void testLinkLayerInferrence() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(IpReachabilityEvent.class),
@@ -182,7 +196,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testDefaultNetworkEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(DefaultNetworkEvent.class),
@@ -223,7 +237,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testDhcpClientEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(DhcpClientEvent.class),
@@ -249,7 +263,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testDhcpErrorEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(DhcpErrorEvent.class),
@@ -274,7 +288,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testIpManagerEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(IpManagerEvent.class),
@@ -300,7 +314,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testIpReachabilityEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(IpReachabilityEvent.class),
@@ -324,7 +338,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testNetworkEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(NetworkEvent.class),
@@ -353,7 +367,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testValidationProbeEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(ValidationProbeEvent.class),
@@ -380,7 +394,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testApfProgramEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(ApfProgramEvent.class),
@@ -414,7 +428,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testApfStatsSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(ApfStats.class),
@@ -457,7 +471,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testRaEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(RaEvent.class),
@@ -490,11 +504,49 @@
verifySerialization(want, ev);
}
+ @Test
+ public void testWakeupStatsSerialization() {
+ WakeupStats stats = new WakeupStats("wlan0");
+ stats.totalWakeups = 14;
+ stats.applicationWakeups = 5;
+ stats.nonApplicationWakeups = 1;
+ stats.rootWakeups = 2;
+ stats.systemWakeups = 3;
+ stats.noUidWakeups = 3;
+
+ IpConnectivityEvent got = IpConnectivityEventBuilder.toProto(stats);
+ String want = String.join("\n",
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 4",
+ " network_id: 0",
+ " time_ms: 0",
+ " transports: 0",
+ " wakeup_stats <",
+ " application_wakeups: 5",
+ " duration_sec: 0",
+ " no_uid_wakeups: 3",
+ " non_application_wakeups: 1",
+ " root_wakeups: 2",
+ " system_wakeups: 3",
+ " total_wakeups: 14",
+ " >",
+ ">",
+ "version: 2\n");
+
+ verifySerialization(want, got);
+ }
+
static void verifySerialization(String want, ConnectivityMetricsEvent... input) {
+ List<IpConnectivityEvent> protoInput =
+ IpConnectivityEventBuilder.toProto(Arrays.asList(input));
+ verifySerialization(want, protoInput.toArray(new IpConnectivityEvent[0]));
+ }
+
+ static void verifySerialization(String want, IpConnectivityEvent... input) {
try {
- List<IpConnectivityEvent> proto =
- IpConnectivityEventBuilder.toProto(Arrays.asList(input));
- byte[] got = IpConnectivityEventBuilder.serialize(0, proto);
+ byte[] got = IpConnectivityEventBuilder.serialize(0, Arrays.asList(input));
IpConnectivityLog log = IpConnectivityLog.parseFrom(got);
assertEquals(want, log.toString());
} catch (Exception e) {
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index cc18b7f..a395c48 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -224,6 +224,15 @@
dnsEvent(101, EVENT_GETADDRINFO, 0, 56);
dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 34);
+ // iface, uid
+ wakeupEvent("wlan0", 1000);
+ wakeupEvent("rmnet0", 10123);
+ wakeupEvent("wlan0", 1000);
+ wakeupEvent("rmnet0", 10008);
+ wakeupEvent("wlan0", -1);
+ wakeupEvent("wlan0", 10008);
+ wakeupEvent("rmnet0", 1000);
+
String want = String.join("\n",
"dropped_events: 0",
"events <",
@@ -405,6 +414,38 @@
" return_codes: 0",
" >",
">",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 2",
+ " network_id: 0",
+ " time_ms: 0",
+ " transports: 0",
+ " wakeup_stats <",
+ " application_wakeups: 2",
+ " duration_sec: 0",
+ " no_uid_wakeups: 0",
+ " non_application_wakeups: 0",
+ " root_wakeups: 0",
+ " system_wakeups: 1",
+ " total_wakeups: 3",
+ " >",
+ ">",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 4",
+ " network_id: 0",
+ " time_ms: 0",
+ " transports: 0",
+ " wakeup_stats <",
+ " application_wakeups: 1",
+ " duration_sec: 0",
+ " no_uid_wakeups: 1",
+ " non_application_wakeups: 0",
+ " root_wakeups: 0",
+ " system_wakeups: 2",
+ " total_wakeups: 4",
+ " >",
+ ">",
"version: 2\n");
verifySerialization(want, getdump("flush"));
@@ -425,6 +466,11 @@
mNetdListener.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
}
+ void wakeupEvent(String iface, int uid) throws Exception {
+ String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface;
+ mNetdListener.onWakeupEvent(prefix, uid, uid, 0);
+ }
+
List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
ArgumentCaptor<ConnectivityMetricsEvent> captor =
ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
new file mode 100644
index 0000000..e3f46a4
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2017 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 com.android.server.connectivity;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.net.ConnectivityManager;
+import android.net.InterfaceConfiguration;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkInfo;
+import android.os.Handler;
+import android.os.INetworkManagementService;
+import android.os.test.TestLooper;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.ConnectivityService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class Nat464XlatTest {
+
+ static final String BASE_IFACE = "test0";
+ static final String STACKED_IFACE = "v4-test0";
+ static final LinkAddress ADDR = new LinkAddress("192.0.2.5/29");
+
+ @Mock ConnectivityService mConnectivity;
+ @Mock INetworkManagementService mNms;
+ @Mock InterfaceConfiguration mConfig;
+ @Mock NetworkAgentInfo mNai;
+
+ TestLooper mLooper;
+ Handler mHandler;
+
+ Nat464Xlat makeNat464Xlat() {
+ return new Nat464Xlat(mNms, mNai);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mLooper = new TestLooper();
+ mHandler = new Handler(mLooper.getLooper());
+
+ MockitoAnnotations.initMocks(this);
+
+ mNai.linkProperties = new LinkProperties();
+ mNai.linkProperties.setInterfaceName(BASE_IFACE);
+ mNai.networkInfo = new NetworkInfo(null);
+ mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI);
+ when(mNai.connService()).thenReturn(mConnectivity);
+ when(mNai.handler()).thenReturn(mHandler);
+
+ when(mNms.getInterfaceConfig(eq(STACKED_IFACE))).thenReturn(mConfig);
+ when(mConfig.getLinkAddress()).thenReturn(ADDR);
+ }
+
+ @Test
+ public void testNormalStartAndStop() throws Exception {
+ Nat464Xlat nat = makeNat464Xlat();
+ ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
+
+ // ConnectivityService starts clat.
+ nat.start();
+
+ verify(mNms).registerObserver(eq(nat));
+ verify(mNms).startClatd(eq(BASE_IFACE));
+
+ // Stacked interface up notification arrives.
+ nat.interfaceLinkStateChanged(STACKED_IFACE, true);
+ mLooper.dispatchNext();
+
+ verify(mNms).getInterfaceConfig(eq(STACKED_IFACE));
+ verify(mNms).setInterfaceIpv6NdOffload(eq(BASE_IFACE), eq(false));
+ verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture());
+ assertFalse(c.getValue().getStackedLinks().isEmpty());
+ assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
+ assertRunning(nat);
+
+ // ConnectivityService stops clat (Network disconnects, IPv4 addr appears, ...).
+ nat.stop();
+
+ verify(mNms).stopClatd(eq(BASE_IFACE));
+ verify(mNms).setInterfaceIpv6NdOffload(eq(BASE_IFACE), eq(true));
+
+ // Stacked interface removed notification arrives.
+ nat.interfaceRemoved(STACKED_IFACE);
+ mLooper.dispatchNext();
+
+ verify(mNms).unregisterObserver(eq(nat));
+ verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture());
+ assertTrue(c.getValue().getStackedLinks().isEmpty());
+ assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
+ assertIdle(nat);
+
+ verifyNoMoreInteractions(mNms, mConnectivity);
+ }
+
+ @Test
+ public void testClatdCrashWhileRunning() throws Exception {
+ Nat464Xlat nat = makeNat464Xlat();
+ ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
+
+ // ConnectivityService starts clat.
+ nat.start();
+
+ verify(mNms).registerObserver(eq(nat));
+ verify(mNms).startClatd(eq(BASE_IFACE));
+
+ // Stacked interface up notification arrives.
+ nat.interfaceLinkStateChanged(STACKED_IFACE, true);
+ mLooper.dispatchNext();
+
+ verify(mNms).getInterfaceConfig(eq(STACKED_IFACE));
+ verify(mNms).setInterfaceIpv6NdOffload(eq(BASE_IFACE), eq(false));
+ verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture());
+ assertFalse(c.getValue().getStackedLinks().isEmpty());
+ assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
+ assertRunning(nat);
+
+ // Stacked interface removed notification arrives (clatd crashed, ...).
+ nat.interfaceRemoved(STACKED_IFACE);
+ mLooper.dispatchNext();
+
+ verify(mNms).unregisterObserver(eq(nat));
+ verify(mNms).stopClatd(eq(BASE_IFACE));
+ verify(mNms).setInterfaceIpv6NdOffload(eq(BASE_IFACE), eq(true));
+ verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture());
+ assertTrue(c.getValue().getStackedLinks().isEmpty());
+ assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
+ assertIdle(nat);
+
+ // ConnectivityService stops clat: no-op.
+ nat.stop();
+
+ verifyNoMoreInteractions(mNms, mConnectivity);
+ }
+
+ @Test
+ public void testStopBeforeClatdStarts() throws Exception {
+ Nat464Xlat nat = makeNat464Xlat();
+
+ // ConnectivityService starts clat.
+ nat.start();
+
+ verify(mNms).registerObserver(eq(nat));
+ verify(mNms).startClatd(eq(BASE_IFACE));
+
+ // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
+ nat.stop();
+
+ verify(mNms).unregisterObserver(eq(nat));
+ verify(mNms).stopClatd(eq(BASE_IFACE));
+ assertIdle(nat);
+
+ // In-flight interface up notification arrives: no-op
+ nat.interfaceLinkStateChanged(STACKED_IFACE, true);
+ mLooper.dispatchNext();
+
+
+ // Interface removed notification arrives after stopClatd() takes effect: no-op.
+ nat.interfaceRemoved(STACKED_IFACE);
+ mLooper.dispatchNext();
+
+ assertIdle(nat);
+
+ verifyNoMoreInteractions(mNms, mConnectivity);
+ }
+
+ @Test
+ public void testStopAndClatdNeverStarts() throws Exception {
+ Nat464Xlat nat = makeNat464Xlat();
+
+ // ConnectivityService starts clat.
+ nat.start();
+
+ verify(mNms).registerObserver(eq(nat));
+ verify(mNms).startClatd(eq(BASE_IFACE));
+
+ // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...)
+ nat.stop();
+
+ verify(mNms).unregisterObserver(eq(nat));
+ verify(mNms).stopClatd(eq(BASE_IFACE));
+ assertIdle(nat);
+
+ verifyNoMoreInteractions(mNms, mConnectivity);
+ }
+
+ static void assertIdle(Nat464Xlat nat) {
+ assertTrue("Nat464Xlat was not IDLE", !nat.isStarted());
+ }
+
+ static void assertRunning(Nat464Xlat nat) {
+ assertTrue("Nat464Xlat was not RUNNING", nat.isRunning());
+ }
+}
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 46f395e..6723601 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -19,6 +19,7 @@
import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
@@ -37,9 +38,11 @@
import android.system.OsConstants;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Base64;
+
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.DNSLookupBatch;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
+
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -47,6 +50,7 @@
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -75,6 +79,118 @@
}
@Test
+ public void testWakeupEventLogging() throws Exception {
+ final int BUFFER_LENGTH = NetdEventListenerService.WAKEUP_EVENT_BUFFER_LENGTH;
+
+ // Assert no events
+ String[] events1 = listNetdEvent();
+ assertEquals(new String[]{""}, events1);
+
+ long now = System.currentTimeMillis();
+ String prefix = "iface:wlan0";
+ int[] uids = { 10001, 10002, 10004, 1000, 10052, 10023, 10002, 10123, 10004 };
+ for (int uid : uids) {
+ mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now);
+ }
+
+ String[] events2 = listNetdEvent();
+ int expectedLength2 = uids.length + 1; // +1 for the WakeupStats line
+ assertEquals(expectedLength2, events2.length);
+ assertContains(events2[0], "WakeupStats");
+ assertContains(events2[0], "wlan0");
+ for (int i = 0; i < uids.length; i++) {
+ String got = events2[i+1];
+ assertContains(got, "WakeupEvent");
+ assertContains(got, "wlan0");
+ assertContains(got, "uid: " + uids[i]);
+ }
+
+ int uid = 20000;
+ for (int i = 0; i < BUFFER_LENGTH * 2; i++) {
+ long ts = now + 10;
+ mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, ts);
+ }
+
+ String[] events3 = listNetdEvent();
+ int expectedLength3 = BUFFER_LENGTH + 1; // +1 for the WakeupStats line
+ assertEquals(expectedLength3, events3.length);
+ assertContains(events2[0], "WakeupStats");
+ assertContains(events2[0], "wlan0");
+ for (int i = 1; i < expectedLength3; i++) {
+ String got = events3[i];
+ assertContains(got, "WakeupEvent");
+ assertContains(got, "wlan0");
+ assertContains(got, "uid: " + uid);
+ }
+
+ uid = 45678;
+ mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now);
+
+ String[] events4 = listNetdEvent();
+ String lastEvent = events4[events4.length - 1];
+ assertContains(lastEvent, "WakeupEvent");
+ assertContains(lastEvent, "wlan0");
+ assertContains(lastEvent, "uid: " + uid);
+ }
+
+ @Test
+ public void testWakeupStatsLogging() throws Exception {
+ wakeupEvent("wlan0", 1000);
+ wakeupEvent("rmnet0", 10123);
+ wakeupEvent("wlan0", 1000);
+ wakeupEvent("rmnet0", 10008);
+ wakeupEvent("wlan0", -1);
+ wakeupEvent("wlan0", 10008);
+ wakeupEvent("rmnet0", 1000);
+ wakeupEvent("wlan0", 10004);
+ wakeupEvent("wlan0", 1000);
+ wakeupEvent("wlan0", 0);
+ wakeupEvent("wlan0", -1);
+ wakeupEvent("rmnet0", 10052);
+ wakeupEvent("wlan0", 0);
+ wakeupEvent("rmnet0", 1000);
+ wakeupEvent("wlan0", 1010);
+
+ String got = flushStatistics();
+ String want = String.join("\n",
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 2",
+ " network_id: 0",
+ " time_ms: 0",
+ " transports: 0",
+ " wakeup_stats <",
+ " application_wakeups: 3",
+ " duration_sec: 0",
+ " no_uid_wakeups: 0",
+ " non_application_wakeups: 0",
+ " root_wakeups: 0",
+ " system_wakeups: 2",
+ " total_wakeups: 5",
+ " >",
+ ">",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 4",
+ " network_id: 0",
+ " time_ms: 0",
+ " transports: 0",
+ " wakeup_stats <",
+ " application_wakeups: 2",
+ " duration_sec: 0",
+ " no_uid_wakeups: 2",
+ " non_application_wakeups: 1",
+ " root_wakeups: 2",
+ " system_wakeups: 3",
+ " total_wakeups: 10",
+ " >",
+ ">",
+ "version: 2\n");
+ assertEquals(want, got);
+ }
+
+ @Test
public void testDnsLogging() throws Exception {
asyncDump(100);
@@ -297,6 +413,11 @@
mNetdEventListenerService.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
}
+ void wakeupEvent(String iface, int uid) throws Exception {
+ String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface;
+ mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, 0);
+ }
+
void asyncDump(long durationMs) throws Exception {
final long stop = System.currentTimeMillis() + durationMs;
final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
@@ -329,4 +450,15 @@
}
return log.toString();
}
+
+ String[] listNetdEvent() throws Exception {
+ StringWriter buffer = new StringWriter();
+ PrintWriter writer = new PrintWriter(buffer);
+ mNetdEventListenerService.list(writer);
+ return buffer.toString().split("\\n");
+ }
+
+ static void assertContains(String got, String want) {
+ assertTrue(got + " did not contain \"" + want + "\"", got.contains(want));
+ }
}
diff --git a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
index 0c2ad38..9c10264 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
@@ -27,7 +27,10 @@
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static com.android.server.net.NetworkStatsCollection.multiplySafe;
+
import android.content.res.Resources;
+import android.net.ConnectivityManager;
import android.net.NetworkIdentity;
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
@@ -40,6 +43,7 @@
import android.test.MoreAsserts;
import android.test.suitebuilder.annotation.SmallTest;
import android.text.format.DateUtils;
+import android.util.RecurrenceRule;
import com.android.frameworks.tests.net.R;
@@ -53,6 +57,9 @@
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
@@ -70,14 +77,27 @@
private static final long TIME_B = 1326110400000L; // UTC: Monday 9th January 2012 12:00:00 PM
private static final long TIME_C = 1326132000000L; // UTC: Monday 9th January 2012 06:00:00 PM
+ private static Clock sOriginalClock;
+
@Override
public void setUp() throws Exception {
super.setUp();
+ sOriginalClock = RecurrenceRule.sClock;
// ignore any device overlay while testing
NetworkTemplate.forceAllNetworkTypes();
}
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ RecurrenceRule.sClock = sOriginalClock;
+ }
+
+ private void setClock(Instant instant) {
+ RecurrenceRule.sClock = Clock.fixed(instant, ZoneId.systemDefault());
+ }
+
public void testReadLegacyNetwork() throws Exception {
final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
stageFile(R.raw.netstats_v1, testFile);
@@ -238,6 +258,9 @@
final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
collection.readLegacyNetwork(testFile);
+ // We're in the future, but not that far off
+ setClock(Instant.parse("2012-06-01T00:00:00.00Z"));
+
// Test a bunch of plans that should result in no augmentation
final List<SubscriptionPlan> plans = new ArrayList<>();
@@ -416,6 +439,28 @@
}
}
+ public void testAugmentPlanGigantic() throws Exception {
+ // We're in the future, but not that far off
+ setClock(Instant.parse("2012-06-01T00:00:00.00Z"));
+
+ // Create a simple history with a ton of measured usage
+ final NetworkStatsCollection large = new NetworkStatsCollection(HOUR_IN_MILLIS);
+ final NetworkIdentitySet ident = new NetworkIdentitySet();
+ ident.add(new NetworkIdentity(ConnectivityManager.TYPE_MOBILE, -1, TEST_IMSI, null,
+ false, true));
+ large.recordData(ident, UID_ALL, SET_ALL, TAG_NONE, TIME_A, TIME_B,
+ new NetworkStats.Entry(12_730_893_164L, 1, 0, 0, 0));
+
+ // Verify untouched total
+ assertEquals(12_730_893_164L, getHistory(large, null, TIME_A, TIME_C).getTotalBytes());
+
+ // Verify anchor that might cause overflows
+ final SubscriptionPlan plan = SubscriptionPlan.Builder
+ .createRecurringMonthly(ZonedDateTime.parse("2012-01-09T00:00:00.00Z"))
+ .setDataUsage(4_939_212_390L, TIME_B).build();
+ assertEquals(4_939_212_386L, getHistory(large, plan, TIME_A, TIME_C).getTotalBytes());
+ }
+
public void testRounding() throws Exception {
final NetworkStatsCollection coll = new NetworkStatsCollection(HOUR_IN_MILLIS);
@@ -437,6 +482,25 @@
assertEquals(TIME_A - HOUR_IN_MILLIS, coll.roundDown(TIME_A - 1));
}
+ public void testMultiplySafe() {
+ assertEquals(25, multiplySafe(50, 1, 2));
+ assertEquals(100, multiplySafe(50, 2, 1));
+
+ assertEquals(-10, multiplySafe(30, -1, 3));
+ assertEquals(0, multiplySafe(30, 0, 3));
+ assertEquals(10, multiplySafe(30, 1, 3));
+ assertEquals(20, multiplySafe(30, 2, 3));
+ assertEquals(30, multiplySafe(30, 3, 3));
+ assertEquals(40, multiplySafe(30, 4, 3));
+
+ assertEquals(100_000_000_000L,
+ multiplySafe(300_000_000_000L, 10_000_000_000L, 30_000_000_000L));
+ assertEquals(100_000_000_010L,
+ multiplySafe(300_000_000_000L, 10_000_000_001L, 30_000_000_000L));
+ assertEquals(823_202_048L,
+ multiplySafe(4_939_212_288L, 2_121_815_528L, 12_730_893_165L));
+ }
+
/**
* Copy a {@link Resources#openRawResource(int)} into {@link File} for
* testing purposes.