summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java14
-rw-r--r--common/networkstackclient/Android.bp1
-rw-r--r--common/networkstackclient/src/android/net/Layer2PacketParcelable.aidl24
-rw-r--r--common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl1
-rw-r--r--common/networkstackclient/src/android/net/ip/IIpClient.aidl1
-rw-r--r--common/networkstackclient/src/android/net/ip/IIpClientCallbacks.aidl6
-rw-r--r--src/android/net/dhcp/DhcpClient.java204
-rw-r--r--src/android/net/ip/IpClient.java224
-rw-r--r--src/com/android/networkstack/netlink/TcpInfo.java4
-rw-r--r--src/com/android/networkstack/netlink/TcpSocketTracker.java84
-rw-r--r--src/com/android/server/connectivity/NetworkMonitor.java47
-rw-r--r--tests/integration/src/android/net/ip/IpClientIntegrationTest.java190
-rw-r--r--tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java12
-rw-r--r--tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java24
-rw-r--r--tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java148
-rw-r--r--tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java14
16 files changed, 796 insertions, 202 deletions
diff --git a/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java b/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java
index 6f9c294..00b4e19 100644
--- a/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java
+++ b/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java
@@ -121,6 +121,14 @@ public class ProvisioningConfiguration {
}
/**
+ * Specify that preconnection feature would be enabled. It's not used by default.
+ */
+ public Builder withPreconnection() {
+ mConfig.mEnablePreconnection = true;
+ return this;
+ }
+
+ /**
* Specify the initial provisioning configuration.
*/
public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
@@ -194,6 +202,7 @@ public class ProvisioningConfiguration {
public boolean mEnableIPv4 = true;
public boolean mEnableIPv6 = true;
+ public boolean mEnablePreconnection = false;
public boolean mUsingMultinetworkPolicyTracker = true;
public boolean mUsingIpReachabilityMonitor = true;
public int mRequestedPreDhcpActionMs;
@@ -210,6 +219,7 @@ public class ProvisioningConfiguration {
public ProvisioningConfiguration(ProvisioningConfiguration other) {
mEnableIPv4 = other.mEnableIPv4;
mEnableIPv6 = other.mEnableIPv6;
+ mEnablePreconnection = other.mEnablePreconnection;
mUsingMultinetworkPolicyTracker = other.mUsingMultinetworkPolicyTracker;
mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
@@ -231,6 +241,7 @@ public class ProvisioningConfiguration {
final ProvisioningConfigurationParcelable p = new ProvisioningConfigurationParcelable();
p.enableIPv4 = mEnableIPv4;
p.enableIPv6 = mEnableIPv6;
+ p.enablePreconnection = mEnablePreconnection;
p.usingMultinetworkPolicyTracker = mUsingMultinetworkPolicyTracker;
p.usingIpReachabilityMonitor = mUsingIpReachabilityMonitor;
p.requestedPreDhcpActionMs = mRequestedPreDhcpActionMs;
@@ -255,6 +266,7 @@ public class ProvisioningConfiguration {
final ProvisioningConfiguration config = new ProvisioningConfiguration();
config.mEnableIPv4 = p.enableIPv4;
config.mEnableIPv6 = p.enableIPv6;
+ config.mEnablePreconnection = p.enablePreconnection;
config.mUsingMultinetworkPolicyTracker = p.usingMultinetworkPolicyTracker;
config.mUsingIpReachabilityMonitor = p.usingIpReachabilityMonitor;
config.mRequestedPreDhcpActionMs = p.requestedPreDhcpActionMs;
@@ -275,6 +287,7 @@ public class ProvisioningConfiguration {
return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
.add("mEnableIPv4: " + mEnableIPv4)
.add("mEnableIPv6: " + mEnableIPv6)
+ .add("mEnablePreconnection: " + mEnablePreconnection)
.add("mUsingMultinetworkPolicyTracker: " + mUsingMultinetworkPolicyTracker)
.add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
.add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
@@ -294,6 +307,7 @@ public class ProvisioningConfiguration {
final ProvisioningConfiguration other = (ProvisioningConfiguration) obj;
return mEnableIPv4 == other.mEnableIPv4
&& mEnableIPv6 == other.mEnableIPv6
+ && mEnablePreconnection == other.mEnablePreconnection
&& mUsingMultinetworkPolicyTracker == other.mUsingMultinetworkPolicyTracker
&& mUsingIpReachabilityMonitor == other.mUsingIpReachabilityMonitor
&& mRequestedPreDhcpActionMs == other.mRequestedPreDhcpActionMs
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp
index d41b470..c1ad011 100644
--- a/common/networkstackclient/Android.bp
+++ b/common/networkstackclient/Android.bp
@@ -50,6 +50,7 @@ aidl_interface {
"src/android/net/INetworkStackConnector.aidl",
"src/android/net/INetworkStackStatusCallback.aidl",
"src/android/net/InitialConfigurationParcelable.aidl",
+ "src/android/net/Layer2PacketParcelable.aidl",
"src/android/net/NattKeepalivePacketDataParcelable.aidl",
"src/android/net/PrivateDnsConfigParcel.aidl",
"src/android/net/ProvisioningConfigurationParcelable.aidl",
diff --git a/common/networkstackclient/src/android/net/Layer2PacketParcelable.aidl b/common/networkstackclient/src/android/net/Layer2PacketParcelable.aidl
new file mode 100644
index 0000000..f49ade4
--- /dev/null
+++ b/common/networkstackclient/src/android/net/Layer2PacketParcelable.aidl
@@ -0,0 +1,24 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.MacAddress;
+
+parcelable Layer2PacketParcelable {
+ MacAddress dstMacAddress;
+ byte[] payload;
+}
diff --git a/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl b/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl
index 99606fb..0b6d7d5 100644
--- a/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl
+++ b/common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl
@@ -35,4 +35,5 @@ parcelable ProvisioningConfigurationParcelable {
int ipv6AddrGenMode;
Network network;
String displayName;
+ boolean enablePreconnection;
}
diff --git a/common/networkstackclient/src/android/net/ip/IIpClient.aidl b/common/networkstackclient/src/android/net/ip/IIpClient.aidl
index 9989c52..ad94aee 100644
--- a/common/networkstackclient/src/android/net/ip/IIpClient.aidl
+++ b/common/networkstackclient/src/android/net/ip/IIpClient.aidl
@@ -35,4 +35,5 @@ oneway interface IIpClient {
void removeKeepalivePacketFilter(int slot);
void setL2KeyAndGroupHint(in String l2Key, in String groupHint);
void addNattKeepalivePacketFilter(int slot, in NattKeepalivePacketDataParcelable pkt);
+ void notifyPreconnectionComplete(boolean success);
}
diff --git a/common/networkstackclient/src/android/net/ip/IIpClientCallbacks.aidl b/common/networkstackclient/src/android/net/ip/IIpClientCallbacks.aidl
index 3681416..de398ed 100644
--- a/common/networkstackclient/src/android/net/ip/IIpClientCallbacks.aidl
+++ b/common/networkstackclient/src/android/net/ip/IIpClientCallbacks.aidl
@@ -15,6 +15,7 @@
*/
package android.net.ip;
+import android.net.Layer2PacketParcelable;
import android.net.LinkProperties;
import android.net.ip.IIpClient;
import android.net.DhcpResultsParcelable;
@@ -63,4 +64,7 @@ oneway interface IIpClientCallbacks {
// Enabled/disable Neighbor Discover offload functionality. This is
// called, for example, whenever 464xlat is being started or stopped.
void setNeighborDiscoveryOffload(boolean enable);
-} \ No newline at end of file
+
+ // Invoked on starting preconnection process.
+ void onPreconnectionStart(in List<Layer2PacketParcelable> packets);
+}
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java
index 9e24ca8..68f7ab0 100644
--- a/src/android/net/dhcp/DhcpClient.java
+++ b/src/android/net/dhcp/DhcpClient.java
@@ -49,6 +49,8 @@ import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
import android.content.Context;
import android.net.DhcpResults;
import android.net.InetAddresses;
+import android.net.Layer2PacketParcelable;
+import android.net.MacAddress;
import android.net.NetworkStackIpMemoryStore;
import android.net.TrafficStats;
import android.net.ip.IpClient;
@@ -169,6 +171,10 @@ public class DhcpClient extends StateMachine {
public static final int CMD_CONFIGURE_LINKADDRESS = PUBLIC_BASE + 8;
public static final int EVENT_LINKADDRESS_CONFIGURED = PUBLIC_BASE + 9;
+ // Command to IpClient starting/aborting preconnection process.
+ public static final int CMD_START_PRECONNECTION = PUBLIC_BASE + 10;
+ public static final int CMD_ABORT_PRECONNECTION = PUBLIC_BASE + 11;
+
/* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */
public static final int DHCP_SUCCESS = 1;
public static final int DHCP_FAILURE = 2;
@@ -239,7 +245,7 @@ public class DhcpClient extends StateMachine {
private DhcpResults mDhcpLease;
private long mDhcpLeaseExpiry;
private DhcpResults mOffer;
- private String mL2Key;
+ private Configuration mConfiguration;
private Inet4Address mLastAssignedIpv4Address;
private long mLastAssignedIpv4AddressExpiry;
private Dependencies mDependencies;
@@ -256,6 +262,7 @@ public class DhcpClient extends StateMachine {
private State mStoppedState = new StoppedState();
private State mDhcpState = new DhcpState();
private State mDhcpInitState = new DhcpInitState();
+ private State mDhcpPreconnectingState = new DhcpPreconnectingState();
private State mDhcpSelectingState = new DhcpSelectingState();
private State mDhcpRequestingState = new DhcpRequestingState();
private State mDhcpHaveLeaseState = new DhcpHaveLeaseState();
@@ -320,6 +327,7 @@ public class DhcpClient extends StateMachine {
addState(mDhcpInitState, mDhcpState);
addState(mWaitBeforeStartState, mDhcpState);
addState(mWaitBeforeObtainingConfigurationState, mDhcpState);
+ addState(mDhcpPreconnectingState, mDhcpState);
addState(mObtainingConfigurationState, mDhcpState);
addState(mDhcpSelectingState, mDhcpState);
addState(mDhcpRequestingState, mDhcpState);
@@ -578,7 +586,7 @@ public class DhcpClient extends StateMachine {
}
private void setLeaseExpiredToIpMemoryStore() {
- final String l2Key = mL2Key;
+ final String l2Key = mConfiguration.l2Key;
if (l2Key == null) return;
final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
// TODO: clear out the address and lease instead of storing an expired lease
@@ -591,7 +599,7 @@ public class DhcpClient extends StateMachine {
}
private void maybeSaveLeaseToIpMemoryStore() {
- final String l2Key = mL2Key;
+ final String l2Key = mConfiguration.l2Key;
if (l2Key == null || mDhcpLease == null || mDhcpLease.ipAddress == null) return;
final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
na.setAssignedV4Address((Inet4Address) mDhcpLease.ipAddress.getAddress());
@@ -711,7 +719,11 @@ public class DhcpClient extends StateMachine {
// Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with
// CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState.
abstract class WaitBeforeOtherState extends LoggingState {
- protected State mOtherState;
+ private final State mOtherState;
+
+ WaitBeforeOtherState(State otherState) {
+ mOtherState = otherState;
+ }
@Override
public void enter() {
@@ -732,18 +744,46 @@ public class DhcpClient extends StateMachine {
}
}
+ /**
+ * Helper method to transition to the appropriate state according to whether the pre dhcp
+ * action (e.g. turn off power optimization while doing DHCP) is required to execute.
+ * waitStateForPreDhcpAction is used to wait the pre dhcp action completed before moving to
+ * other state. If the pre dhcp action is unnecessary, transition to the target state directly.
+ */
+ private void preDhcpTransitionTo(final State waitStateForPreDhcpAction,
+ final State targetState) {
+ transitionTo(mRegisteredForPreDhcpNotification ? waitStateForPreDhcpAction : targetState);
+ }
+
+ /**
+ * This class is used to convey initial configuration to DhcpClient when starting DHCP.
+ */
+ public static class Configuration {
+ // This is part of the initial configuration because it is passed in on startup and
+ // never updated.
+ // TODO: decide what to do about L2 key changes while the client is connected.
+ public final String l2Key;
+ public final boolean isPreconnectionEnabled;
+
+ public Configuration(String l2Key, boolean isPreconnectionEnabled) {
+ this.l2Key = l2Key;
+ this.isPreconnectionEnabled = isPreconnectionEnabled;
+ }
+ }
+
class StoppedState extends State {
@Override
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_START_DHCP:
- mL2Key = (String) message.obj;
- if (mRegisteredForPreDhcpNotification) {
- transitionTo(isDhcpLeaseCacheEnabled()
- ? mWaitBeforeObtainingConfigurationState : mWaitBeforeStartState);
+ mConfiguration = (Configuration) message.obj;
+ if (mConfiguration.isPreconnectionEnabled) {
+ transitionTo(mDhcpPreconnectingState);
+ } else if (isDhcpLeaseCacheEnabled()) {
+ preDhcpTransitionTo(mWaitBeforeObtainingConfigurationState,
+ mObtainingConfigurationState);
} else {
- transitionTo(isDhcpLeaseCacheEnabled()
- ? mObtainingConfigurationState : mDhcpInitState);
+ preDhcpTransitionTo(mWaitBeforeStartState, mDhcpInitState);
}
return HANDLED;
default:
@@ -754,22 +794,19 @@ public class DhcpClient extends StateMachine {
class WaitBeforeStartState extends WaitBeforeOtherState {
WaitBeforeStartState(State otherState) {
- super();
- mOtherState = otherState;
+ super(otherState);
}
}
class WaitBeforeRenewalState extends WaitBeforeOtherState {
WaitBeforeRenewalState(State otherState) {
- super();
- mOtherState = otherState;
+ super(otherState);
}
}
class WaitBeforeObtainingConfigurationState extends WaitBeforeOtherState {
WaitBeforeObtainingConfigurationState(State otherState) {
- super();
- mOtherState = otherState;
+ super(otherState);
}
}
@@ -957,7 +994,7 @@ public class DhcpClient extends StateMachine {
}
sendMessage(EVENT_CONFIGURATION_OBTAINED, attributes);
};
- mIpMemoryStore.retrieveNetworkAttributes(mL2Key, listener);
+ mIpMemoryStore.retrieveNetworkAttributes(mConfiguration.l2Key, listener);
}
@Override
@@ -973,7 +1010,7 @@ public class DhcpClient extends StateMachine {
final long currentTime = System.currentTimeMillis();
NetworkAttributes attributes = (NetworkAttributes) message.obj;
if (DBG) {
- Log.d(TAG, "l2key: " + mL2Key
+ Log.d(TAG, "l2key: " + mConfiguration.l2Key
+ " lease address: " + attributes.assignedV4Address
+ " lease expiry: " + attributes.assignedV4AddressExpiry
+ " current time: " + currentTime);
@@ -1002,6 +1039,32 @@ public class DhcpClient extends StateMachine {
}
}
+ private void receiveOfferOrAckPacket(final DhcpPacket packet, final boolean acceptRapidCommit) {
+ if (!isValidPacket(packet)) return;
+
+ // 1. received the DHCPOFFER packet, process it by following RFC2131.
+ // 2. received the DHCPACK packet from DHCP Servers that support Rapid
+ // Commit option, process it by following RFC4039.
+ if (packet instanceof DhcpOfferPacket) {
+ mOffer = packet.toDhcpResults();
+ if (mOffer != null) {
+ Log.d(TAG, "Got pending lease: " + mOffer);
+ transitionTo(mDhcpRequestingState);
+ }
+ } else if (packet instanceof DhcpAckPacket) {
+ // If rapid commit is not enabled in DhcpInitState, or enablePreconnection is
+ // not enabled in DhcpPreconnectingState, ignore DHCPACK packet. Only DHCPACK
+ // with the rapid commit option are valid.
+ if (!acceptRapidCommit || !packet.mRapidCommit) return;
+
+ final DhcpResults results = packet.toDhcpResults();
+ if (results != null) {
+ confirmDhcpLease(packet, results);
+ transitionTo(mConfiguringInterfaceState);
+ }
+ }
+ }
+
class DhcpInitState extends PacketRetransmittingState {
public DhcpInitState() {
super();
@@ -1019,27 +1082,86 @@ public class DhcpClient extends StateMachine {
}
protected void receivePacket(DhcpPacket packet) {
- if (!isValidPacket(packet)) return;
+ receiveOfferOrAckPacket(packet, isDhcpRapidCommitEnabled());
+ }
+ }
- // 1. received the DHCPOFFER packet, process it by following RFC2131.
- // 2. received the DHCPACK packet from DHCP Servers who support Rapid
- // Commit option, process it by following RFC4039.
- if (packet instanceof DhcpOfferPacket) {
- mOffer = packet.toDhcpResults();
- if (mOffer != null) {
- Log.d(TAG, "Got pending lease: " + mOffer);
- transitionTo(mDhcpRequestingState);
- }
- } else if (packet instanceof DhcpAckPacket) {
- // If received DHCPACK packet w/o Rapid Commit option in this state,
- // just drop it and wait for the next DHCPOFFER packet or DHCPACK w/
- // Rapid Commit option.
- if (!isDhcpRapidCommitEnabled() || !packet.mRapidCommit) return;
- final DhcpResults results = packet.toDhcpResults();
- if (results != null) {
- confirmDhcpLease(packet, results);
- transitionTo(mConfiguringInterfaceState);
- }
+ private void startInitRebootOrInit() {
+ if (isDhcpLeaseCacheEnabled()) {
+ preDhcpTransitionTo(mWaitBeforeObtainingConfigurationState,
+ mObtainingConfigurationState);
+ } else {
+ preDhcpTransitionTo(mWaitBeforeStartState, mDhcpInitState);
+ }
+ }
+
+ class DhcpPreconnectingState extends TimeoutState {
+ // This state is used to support Fast Initial Link Setup (FILS) IP Address Setup
+ // procedure defined in the IEEE802.11ai (2016) currently. However, this state could
+ // be extended to support other intended useage as well in the future, e.g. pre-actions
+ // should be completed in advance before the normal DHCP solicit process starts.
+ DhcpPreconnectingState() {
+ mTimeout = FIRST_TIMEOUT_MS;
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+ startNewTransaction();
+ mLastInitEnterTime = SystemClock.elapsedRealtime();
+ sendPreconnectionPacket();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (super.processMessage(message) == HANDLED) {
+ return HANDLED;
+ }
+
+ switch (message.what) {
+ case CMD_RECEIVED_PACKET:
+ receiveOfferOrAckPacket((DhcpPacket) message.obj,
+ mConfiguration.isPreconnectionEnabled);
+ return HANDLED;
+ case CMD_ABORT_PRECONNECTION:
+ startInitRebootOrInit();
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+
+ // This timeout is necessary and cannot just be replaced with a notification that
+ // preconnection is complete. This is because:
+ // - The preconnection complete notification could arrive before the ACK with rapid
+ // commit arrives. In this case we would go back to init state, pick a new transaction
+ // ID, and when the ACK with rapid commit arrives, we would ignore it because the
+ // transaction ID doesn't match.
+ // - We cannot just wait in this state until the ACK with rapid commit arrives, because
+ // if that ACK never arrives (e.g., dropped by the network), we'll never go back to init
+ // and send a DISCOVER.
+ @Override
+ public void timeout() {
+ startInitRebootOrInit();
+ }
+
+ private void sendPreconnectionPacket() {
+ final Layer2PacketParcelable l2Packet = new Layer2PacketParcelable();
+ final ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
+ DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
+ DO_UNICAST, REQUESTED_PARAMS, true /* rapid commit */);
+
+ l2Packet.dstMacAddress = MacAddress.fromBytes(DhcpPacket.ETHER_BROADCAST);
+ l2Packet.payload = packet.array();
+ mController.sendMessage(CMD_START_PRECONNECTION, l2Packet);
+ }
+
+ private void startInitRebootOrInit() {
+ if (isDhcpLeaseCacheEnabled()) {
+ preDhcpTransitionTo(mWaitBeforeObtainingConfigurationState,
+ mObtainingConfigurationState);
+ } else {
+ preDhcpTransitionTo(mWaitBeforeStartState, mDhcpInitState);
}
}
}
@@ -1165,11 +1287,7 @@ public class DhcpClient extends StateMachine {
super.processMessage(message);
switch (message.what) {
case CMD_RENEW_DHCP:
- if (mRegisteredForPreDhcpNotification) {
- transitionTo(mWaitBeforeRenewalState);
- } else {
- transitionTo(mDhcpRenewingState);
- }
+ preDhcpTransitionTo(mWaitBeforeRenewalState, mDhcpRenewingState);
return HANDLED;
default:
return NOT_HANDLED;
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 8808ee2..8e95ab5 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -27,6 +27,7 @@ import android.net.ConnectivityManager;
import android.net.DhcpResults;
import android.net.INetd;
import android.net.IpPrefix;
+import android.net.Layer2PacketParcelable;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NattKeepalivePacketDataParcelable;
@@ -38,6 +39,7 @@ import android.net.TcpKeepalivePacketDataParcelable;
import android.net.apf.ApfCapabilities;
import android.net.apf.ApfFilter;
import android.net.dhcp.DhcpClient;
+import android.net.dhcp.DhcpClient.Configuration;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.IpManagerEvent;
import android.net.shared.InitialConfiguration;
@@ -76,6 +78,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -178,6 +181,10 @@ public class IpClient extends StateMachine {
mLog.e(PREFIX + msg, e);
}
+ /**
+ * Callback called prior to DHCP discovery/renewal only if the pre DHCP action
+ * is enabled.
+ */
public void onPreDhcpAction() {
log("onPreDhcpAction()");
try {
@@ -187,6 +194,10 @@ public class IpClient extends StateMachine {
}
}
+ /**
+ * Callback called after DHCP discovery/renewal only if the pre DHCP action
+ * is enabled.
+ */
public void onPostDhcpAction() {
log("onPostDhcpAction()");
try {
@@ -196,6 +207,9 @@ public class IpClient extends StateMachine {
}
}
+ /**
+ * Callback called when new DHCP results are available.
+ */
public void onNewDhcpResults(DhcpResults dhcpResults) {
log("onNewDhcpResults({" + dhcpResults + "})");
try {
@@ -205,6 +219,9 @@ public class IpClient extends StateMachine {
}
}
+ /**
+ * Indicates that provisioning was successful.
+ */
public void onProvisioningSuccess(LinkProperties newLp) {
log("onProvisioningSuccess({" + newLp + "})");
try {
@@ -214,6 +231,9 @@ public class IpClient extends StateMachine {
}
}
+ /**
+ * Indicates that provisioning failed.
+ */
public void onProvisioningFailure(LinkProperties newLp) {
log("onProvisioningFailure({" + newLp + "})");
try {
@@ -223,6 +243,9 @@ public class IpClient extends StateMachine {
}
}
+ /**
+ * Invoked on LinkProperties changes.
+ */
public void onLinkPropertiesChange(LinkProperties newLp) {
log("onLinkPropertiesChange({" + newLp + "})");
try {
@@ -232,6 +255,10 @@ public class IpClient extends StateMachine {
}
}
+ /**
+ * Called when the internal IpReachabilityMonitor (if enabled) has detected the loss of
+ * required neighbors (e.g. on-link default gw or dns servers) due to NUD_FAILED.
+ */
public void onReachabilityLost(String logMsg) {
log("onReachabilityLost(" + logMsg + ")");
try {
@@ -241,6 +268,9 @@ public class IpClient extends StateMachine {
}
}
+ /**
+ * Called when the IpClient state machine terminates.
+ */
public void onQuit() {
log("onQuit()");
try {
@@ -250,6 +280,9 @@ public class IpClient extends StateMachine {
}
}
+ /**
+ * Called to indicate that a new APF program must be installed to filter incoming packets.
+ */
public void installPacketFilter(byte[] filter) {
log("installPacketFilter(byte[" + filter.length + "])");
try {
@@ -259,6 +292,10 @@ public class IpClient extends StateMachine {
}
}
+ /**
+ * Called to indicate that the APF Program & data buffer must be read asynchronously from
+ * the wifi driver.
+ */
public void startReadPacketFilter() {
log("startReadPacketFilter()");
try {
@@ -268,6 +305,10 @@ public class IpClient extends StateMachine {
}
}
+ /**
+ * If multicast filtering cannot be accomplished with APF, this function will be called to
+ * actuate multicast filtering using another means.
+ */
public void setFallbackMulticastFilter(boolean enabled) {
log("setFallbackMulticastFilter(" + enabled + ")");
try {
@@ -277,6 +318,10 @@ public class IpClient extends StateMachine {
}
}
+ /**
+ * Enabled/disable Neighbor Discover offload functionality. This is called, for example,
+ * whenever 464xlat is being started or stopped.
+ */
public void setNeighborDiscoveryOffload(boolean enable) {
log("setNeighborDiscoveryOffload(" + enable + ")");
try {
@@ -285,6 +330,18 @@ public class IpClient extends StateMachine {
log("Failed to call setNeighborDiscoveryOffload", e);
}
}
+
+ /**
+ * Invoked on starting preconnection process.
+ */
+ public void onPreconnectionStart(List<Layer2PacketParcelable> packets) {
+ log("onPreconnectionStart(Layer2Packets[" + packets.size() + "])");
+ try {
+ mCallback.onPreconnectionStart(packets);
+ } catch (RemoteException e) {
+ log("Failed to call onPreconnectionStart", e);
+ }
+ }
}
public static final String DUMP_ARG_CONFIRM = "confirm";
@@ -306,11 +363,12 @@ public class IpClient extends StateMachine {
private static final int CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF = 13;
private static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF = 14;
private static final int CMD_UPDATE_L2KEY_GROUPHINT = 15;
+ protected static final int CMD_COMPLETE_PRECONNECTION = 16;
// Internal commands to use instead of trying to call transitionTo() inside
// a given State's enter() method. Calling transitionTo() from enter/exit
// encounters a Log.wtf() that can cause trouble on eng builds.
- private static final int CMD_JUMP_STARTED_TO_RUNNING = 100;
+ private static final int CMD_ADDRESSES_CLEARED = 100;
private static final int CMD_JUMP_RUNNING_TO_STOPPING = 101;
private static final int CMD_JUMP_STOPPING_TO_STOPPED = 102;
@@ -342,8 +400,10 @@ public class IpClient extends StateMachine {
private final State mStoppedState = new StoppedState();
private final State mStoppingState = new StoppingState();
+ private final State mClearingIpAddressesState = new ClearingIpAddressesState();
private final State mStartedState = new StartedState();
private final State mRunningState = new RunningState();
+ private final State mPreconnectingState = new PreconnectingState();
private final String mTag;
private final Context mContext;
@@ -606,6 +666,11 @@ public class IpClient extends StateMachine {
enforceNetworkStackCallingPermission();
IpClient.this.removeKeepalivePacketFilter(slot);
}
+ @Override
+ public void notifyPreconnectionComplete(boolean success) {
+ enforceNetworkStackCallingPermission();
+ IpClient.this.notifyPreconnectionComplete(success);
+ }
@Override
public int getInterfaceVersion() {
@@ -621,6 +686,8 @@ public class IpClient extends StateMachine {
// CHECKSTYLE:OFF IndentationCheck
addState(mStoppedState);
addState(mStartedState);
+ addState(mPreconnectingState, mStartedState);
+ addState(mClearingIpAddressesState, mStartedState);
addState(mRunningState, mStartedState);
addState(mStoppingState);
// CHECKSTYLE:ON IndentationCheck
@@ -765,6 +832,15 @@ public class IpClient extends StateMachine {
}
/**
+ * Notify IpClient that preconnection is complete and that the link is ready for use.
+ * The success parameter indicates whether the packets passed in by onPreconnectionStart were
+ * successfully sent to the network or not.
+ */
+ public void notifyPreconnectionComplete(boolean success) {
+ sendMessage(CMD_COMPLETE_PRECONNECTION, success ? 1 : 0);
+ }
+
+ /**
* Dump logs of this IpClient.
*/
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
@@ -1227,11 +1303,10 @@ public class IpClient extends StateMachine {
return false;
}
} else {
- // Start DHCPv4.
- mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceParams,
- mDependencies.getDhcpClientDependencies(mIpMemoryStore));
- mDhcpClient.registerForPreDhcpNotification();
- mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP, mL2Key);
+ if (mDhcpClient != null) {
+ Log.wtf(mTag, "DhcpClient should never be non-null in startIPv4()");
+ }
+ startDhcpClient();
}
return true;
@@ -1355,7 +1430,7 @@ public class IpClient extends StateMachine {
case CMD_START:
mConfiguration = (android.net.shared.ProvisioningConfiguration) msg.obj;
- transitionTo(mStartedState);
+ transitionTo(mClearingIpAddressesState);
break;
case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
@@ -1437,18 +1512,30 @@ public class IpClient extends StateMachine {
}
}
- class StartedState extends State {
+ private boolean isUsingPreconnection() {
+ return mConfiguration.mEnablePreconnection && mConfiguration.mStaticIpConfig == null;
+ }
+
+ private void startDhcpClient() {
+ // Start DHCPv4.
+ mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceParams,
+ mDependencies.getDhcpClientDependencies(mIpMemoryStore));
+
+ // If preconnection is enabled, there is no need to ask Wi-Fi to disable powersaving
+ // during DHCP, because the DHCP handshake will happen during association. In order to
+ // ensure that future renews still do the DHCP action (if configured),
+ // registerForPreDhcpNotification is called later when processing the CMD_*_PRECONNECTION
+ // messages.
+ if (!isUsingPreconnection()) mDhcpClient.registerForPreDhcpNotification();
+ mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP, new Configuration(mL2Key,
+ isUsingPreconnection()));
+ }
+
+ class ClearingIpAddressesState extends State {
@Override
public void enter() {
- mStartTimeMillis = SystemClock.elapsedRealtime();
- if (mConfiguration.mProvisioningTimeoutMs > 0) {
- final long alarmTime = SystemClock.elapsedRealtime()
- + mConfiguration.mProvisioningTimeoutMs;
- mProvisioningTimeoutAlarm.schedule(alarmTime);
- }
-
if (readyToProceed()) {
- deferMessage(obtainMessage(CMD_JUMP_STARTED_TO_RUNNING));
+ deferMessage(obtainMessage(CMD_ADDRESSES_CLEARED));
} else {
// Clear all IPv4 and IPv6 before proceeding to RunningState.
// Clean up any leftover state from an abnormal exit from
@@ -1458,26 +1545,97 @@ public class IpClient extends StateMachine {
}
@Override
- public void exit() {
- mProvisioningTimeoutAlarm.cancel();
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_ADDRESSES_CLEARED:
+ transitionTo(isUsingPreconnection() ? mPreconnectingState : mRunningState);
+ break;
+
+ case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+ handleLinkPropertiesUpdate(NO_CALLBACKS);
+ if (readyToProceed()) {
+ transitionTo(mRunningState);
+ }
+ break;
+
+ case CMD_STOP:
+ case EVENT_PROVISIONING_TIMEOUT:
+ // Fall through to StartedState.
+ return NOT_HANDLED;
+
+ default:
+ // It's safe to process messages out of order because the
+ // only message that can both
+ // a) be received at this time and
+ // b) affect provisioning state
+ // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
+ deferMessage(msg);
+ }
+ return HANDLED;
+ }
+
+ private boolean readyToProceed() {
+ return (!mLinkProperties.hasIpv4Address() && !mLinkProperties.hasGlobalIpv6Address());
+ }
+ }
+
+ class PreconnectingState extends State {
+ @Override
+ public void enter() {
+ startDhcpClient();
}
@Override
public boolean processMessage(Message msg) {
switch (msg.what) {
- case CMD_JUMP_STARTED_TO_RUNNING:
+ case CMD_COMPLETE_PRECONNECTION:
+ boolean success = (msg.arg1 == 1);
+ mDhcpClient.registerForPreDhcpNotification();
+ if (!success) {
+ mDhcpClient.sendMessage(DhcpClient.CMD_ABORT_PRECONNECTION);
+ }
+ // The link is ready for use. Advance to running state, start IPv6, etc.
transitionTo(mRunningState);
break;
- case CMD_STOP:
- transitionTo(mStoppingState);
+ case DhcpClient.CMD_START_PRECONNECTION:
+ final Layer2PacketParcelable l2Packet = (Layer2PacketParcelable) msg.obj;
+ mCallback.onPreconnectionStart(Collections.singletonList(l2Packet));
break;
- case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
- handleLinkPropertiesUpdate(NO_CALLBACKS);
- if (readyToProceed()) {
- transitionTo(mRunningState);
- }
+ case CMD_STOP:
+ case EVENT_PROVISIONING_TIMEOUT:
+ // Fall through to StartedState.
+ return NOT_HANDLED;
+
+ default:
+ deferMessage(msg);
+ }
+ return HANDLED;
+ }
+ }
+
+ class StartedState extends State {
+ @Override
+ public void enter() {
+ mStartTimeMillis = SystemClock.elapsedRealtime();
+ if (mConfiguration.mProvisioningTimeoutMs > 0) {
+ final long alarmTime = SystemClock.elapsedRealtime()
+ + mConfiguration.mProvisioningTimeoutMs;
+ mProvisioningTimeoutAlarm.schedule(alarmTime);
+ }
+ }
+
+ @Override
+ public void exit() {
+ mProvisioningTimeoutAlarm.cancel();
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_STOP:
+ transitionTo(mStoppingState);
break;
case CMD_UPDATE_L2KEY_GROUPHINT: {
@@ -1497,22 +1655,14 @@ public class IpClient extends StateMachine {
case EVENT_PROVISIONING_TIMEOUT:
handleProvisioningFailure();
break;
+
default:
- // It's safe to process messages out of order because the
- // only message that can both
- // a) be received at this time and
- // b) affect provisioning state
- // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
- deferMessage(msg);
+ return NOT_HANDLED;
}
mMsgStateLogger.handled(this, getCurrentState());
return HANDLED;
}
-
- private boolean readyToProceed() {
- return (!mLinkProperties.hasIpv4Address() && !mLinkProperties.hasGlobalIpv6Address());
- }
}
class RunningState extends State {
@@ -1544,7 +1694,7 @@ public class IpClient extends StateMachine {
return;
}
- if (mConfiguration.mEnableIPv4 && !startIPv4()) {
+ if (mConfiguration.mEnableIPv4 && !isUsingPreconnection() && !startIPv4()) {
doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
enqueueJumpToStoppingState();
return;
diff --git a/src/com/android/networkstack/netlink/TcpInfo.java b/src/com/android/networkstack/netlink/TcpInfo.java
index f2812e4..e6036b5 100644
--- a/src/com/android/networkstack/netlink/TcpInfo.java
+++ b/src/com/android/networkstack/netlink/TcpInfo.java
@@ -112,6 +112,10 @@ public class TcpInfo {
}
}
mFieldsValues = Collections.unmodifiableMap(fields);
+ // tcp_info structure grows over time as new fields are added. Jump to the end of the
+ // structure, as unknown fields might remain at the end of the structure if the tcp_info
+ // struct was expanded.
+ bytes.position(Math.min(infolen + start, bytes.limit()));
}
@VisibleForTesting
diff --git a/src/com/android/networkstack/netlink/TcpSocketTracker.java b/src/com/android/networkstack/netlink/TcpSocketTracker.java
index fc762cb..8f24aec 100644
--- a/src/com/android/networkstack/netlink/TcpSocketTracker.java
+++ b/src/com/android/networkstack/netlink/TcpSocketTracker.java
@@ -17,9 +17,9 @@ package com.android.networkstack.netlink;
import static android.net.netlink.InetDiagMessage.InetDiagReqV2;
import static android.net.netlink.NetlinkConstants.INET_DIAG_MEMINFO;
-import static android.net.netlink.NetlinkConstants.NLA_ALIGNTO;
import static android.net.netlink.NetlinkConstants.NLMSG_DONE;
import static android.net.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE;
+import static android.net.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
import static android.net.util.DataStallUtils.CONFIG_MIN_PACKETS_THRESHOLD;
@@ -39,6 +39,7 @@ import static android.system.OsConstants.SOL_SOCKET;
import static android.system.OsConstants.SO_SNDTIMEO;
import android.content.Context;
+import android.net.netlink.NetlinkConstants;
import android.net.netlink.NetlinkSocket;
import android.net.netlink.StructInetDiagMsg;
import android.net.netlink.StructNlMsgHdr;
@@ -62,8 +63,10 @@ import com.android.internal.annotations.VisibleForTesting;
import java.io.FileDescriptor;
import java.io.InterruptedIOException;
import java.net.SocketException;
+import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
+import java.util.Base64;
import java.util.List;
/**
@@ -170,33 +173,48 @@ public class TcpSocketTracker {
// | Netlink Header | Family Header | Attributes | rtattr |
// | struct nlmsghdr | struct rtmsg | struct rtattr| data |
// +------------------+---------------+--------------+--------+
- final ByteBuffer bytes = mDependencies.recvMesssage(fd);
-
- while (enoughBytesRemainForValidNlMsg(bytes)) {
- final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(bytes);
- if (nlmsghdr == null) {
- Log.e(TAG, "Badly formatted data.");
- break;
- }
- final int nlmsgLen = nlmsghdr.nlmsg_len;
- log("pollSocketsInfo: nlmsghdr=" + nlmsghdr);
- if (nlmsghdr.nlmsg_type == NLMSG_DONE) break;
-
- if (isValidInetDiagMsgSize(nlmsgLen)) {
- // Get the socket cookie value. Composed by two Integers value.
- // Corresponds to inet_diag_sockid in
- // &lt;linux_src&gt;/include/uapi/linux/inet_diag.h
- bytes.position(bytes.position() + IDIAG_COOKIE_OFFSET);
- // It's stored in native with 2 int. Parse it as long for convenience.
- final long cookie = bytes.getLong();
- // Skip the rest part of StructInetDiagMsg.
- bytes.position(bytes.position()
- + StructInetDiagMsg.STRUCT_SIZE - IDIAG_COOKIE_OFFSET - Long.BYTES);
- final SocketInfo info = parseSockInfo(bytes, family, nlmsgLen, time);
- // Update TcpStats based on previous and current socket info.
- stat.accumulate(calculateLatestPacketsStat(info, mSocketInfos.get(cookie)));
- mSocketInfos.put(cookie, info);
+ final ByteBuffer bytes = mDependencies.recvMessage(fd);
+ try {
+ while (enoughBytesRemainForValidNlMsg(bytes)) {
+ final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(bytes);
+ if (nlmsghdr == null) {
+ Log.e(TAG, "Badly formatted data.");
+ break;
+ }
+ final int nlmsgLen = nlmsghdr.nlmsg_len;
+ log("pollSocketsInfo: nlmsghdr=" + nlmsghdr + ", limit=" + bytes.limit());
+ // End of the message. Stop parsing.
+ if (nlmsghdr.nlmsg_type == NLMSG_DONE) break;
+
+ if (nlmsghdr.nlmsg_type != SOCK_DIAG_BY_FAMILY) {
+ Log.e(TAG, "Expect to get family " + family
+ + " SOCK_DIAG_BY_FAMILY message but get "
+ + nlmsghdr.nlmsg_type);
+ break;
+ }
+
+ if (isValidInetDiagMsgSize(nlmsgLen)) {
+ // Get the socket cookie value. Composed by two Integers value.
+ // Corresponds to inet_diag_sockid in
+ // &lt;linux_src&gt;/include/uapi/linux/inet_diag.h
+ bytes.position(bytes.position() + IDIAG_COOKIE_OFFSET);
+ // It's stored in native with 2 int. Parse it as long for convenience.
+ final long cookie = bytes.getLong();
+ // Skip the rest part of StructInetDiagMsg.
+ bytes.position(bytes.position()
+ + StructInetDiagMsg.STRUCT_SIZE - IDIAG_COOKIE_OFFSET
+ - Long.BYTES);
+ final SocketInfo info = parseSockInfo(bytes, family, nlmsgLen, time);
+ // Update TcpStats based on previous and current socket info.
+ stat.accumulate(
+ calculateLatestPacketsStat(info, mSocketInfos.get(cookie)));
+ mSocketInfos.put(cookie, info);
+ }
}
+ } catch (IllegalArgumentException | BufferUnderflowException e) {
+ Log.wtf(TAG, "Unexpected socket info parsing, family " + family
+ + " buffer:" + bytes + " "
+ + Base64.getEncoder().encodeToString(bytes.array()), e);
}
}
// Calculate mLatestReceiveCount, mSentSinceLastRecv and mLatestPacketFailPercentage.
@@ -244,7 +262,7 @@ public class TcpSocketTracker {
while (bytes.position() < remainingDataSize) {
final RoutingAttribute rtattr =
new RoutingAttribute(bytes.getShort(), bytes.getShort());
- final int dataLen = rtattr.getDataLength();
+ final short dataLen = rtattr.getDataLength();
if (rtattr.rtaType == RoutingAttribute.INET_DIAG_INFO) {
tcpInfo = TcpInfo.parse(bytes, dataLen);
} else if (rtattr.rtaType == RoutingAttribute.INET_DIAG_MARK) {
@@ -350,7 +368,7 @@ public class TcpSocketTracker {
* @param len the remaining length to skip.
*/
private void skipRemainingAttributesBytesAligned(@NonNull final ByteBuffer buffer,
- final int len) {
+ final short len) {
// Data in {@Code RoutingAttribute} is followed after header with size {@Code NLA_ALIGNTO}
// bytes long for each block. Next attribute will start after the padding bytes if any.
// If all remaining bytes after header are valid in a data block, next attr will just start
@@ -364,7 +382,7 @@ public class TcpSocketTracker {
// [HEADER(L=5)][ 4-Bytes DATA ][ HEADER(L=8) ][4 bytes DATA][Next attr]
// [ 5 valid bytes ][3 padding bytes ][ 8 valid bytes ] ...
final int cur = buffer.position();
- buffer.position(cur + ((len + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1)));
+ buffer.position(cur + NetlinkConstants.alignedLengthOf(len));
}
private void log(final String str) {
@@ -393,8 +411,8 @@ public class TcpSocketTracker {
rtaLen = len;
rtaType = type;
}
- public int getDataLength() {
- return rtaLen - HEADER_LENGTH;
+ public short getDataLength() {
+ return (short) (rtaLen - HEADER_LENGTH);
}
}
@@ -527,7 +545,7 @@ public class TcpSocketTracker {
/**
* Receive the request message from kernel via given fd.
*/
- public ByteBuffer recvMesssage(@NonNull final FileDescriptor fd)
+ public ByteBuffer recvMessage(@NonNull final FileDescriptor fd)
throws ErrnoException, InterruptedIOException {
return NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT);
}
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index 3dfe004..ba881e4 100644
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -144,6 +144,7 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Random;
+import java.util.StringJoiner;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -390,15 +391,15 @@ public class NetworkMonitor extends StateMachine {
public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
SharedLog validationLog) {
this(context, cb, network, new IpConnectivityLog(), validationLog,
- Dependencies.DEFAULT, new DataStallStatsUtils(), new TcpSocketTracker(
- new TcpSocketTracker.Dependencies(context,
- ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q))));
+ Dependencies.DEFAULT, new DataStallStatsUtils(),
+ getTcpSocketTrackerOrNull(context));
}
@VisibleForTesting
public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
IpConnectivityLog logger, SharedLog validationLogs,
- Dependencies deps, DataStallStatsUtils detectionStatsUtils, TcpSocketTracker tst) {
+ Dependencies deps, DataStallStatsUtils detectionStatsUtils,
+ @Nullable TcpSocketTracker tst) {
// Add suffix indicating which NetworkMonitor we're talking about.
super(TAG + "/" + network.toString());
@@ -758,8 +759,10 @@ public class NetworkMonitor extends StateMachine {
}
break;
case EVENT_POLL_TCPINFO:
+ final TcpSocketTracker tst = getTcpSocketTracker();
+ if (tst == null) break;
// Transit if retrieve socket info is succeeded and suspected as a stall.
- if (getTcpSocketTracker().pollSocketsInfo() && evaluateDataStall()) {
+ if (tst.pollSocketsInfo() && evaluateDataStall()) {
transitionTo(mEvaluatingState);
} else {
sendTcpPollingEvent();
@@ -2114,6 +2117,7 @@ public class NetworkMonitor extends StateMachine {
}
@VisibleForTesting
+ @Nullable
protected TcpSocketTracker getTcpSocketTracker() {
return mTcpTracker;
}
@@ -2130,6 +2134,7 @@ public class NetworkMonitor extends StateMachine {
@VisibleForTesting
protected boolean isDataStall() {
Boolean result = null;
+ final StringJoiner msg = VDBG_STALL ? new StringJoiner(", ") : null;
// Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the
// possible traffic cost in metered network.
if (!mNetworkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
@@ -2141,12 +2146,17 @@ public class NetworkMonitor extends StateMachine {
// 1. TCP connection fail rate(lost+retrans) is higher than threshold.
// 2. Accumulate enough packets count.
// TODO: Need to filter per target network.
- if (dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_TCP)) {
- if (getTcpSocketTracker().getLatestReceivedCount() > 0) {
+ final TcpSocketTracker tst = getTcpSocketTracker();
+ if (dataStallEvaluateTypeEnabled(DATA_STALL_EVALUATION_TYPE_TCP) && tst != null) {
+ if (tst.getLatestReceivedCount() > 0) {
result = false;
- } else if (getTcpSocketTracker().isDataStallSuspected()) {
+ } else if (tst.isDataStallSuspected()) {
result = true;
}
+ if (VDBG_STALL) {
+ msg.add("tcp packets received=" + tst.getLatestReceivedCount())
+ .add("tcp fail rate=" + tst.getLatestPacketFailPercentage());
+ }
}
// Check dns signal. Suspect it may be a data stall if both :
@@ -2158,13 +2168,14 @@ public class NetworkMonitor extends StateMachine {
result = true;
logNetworkEvent(NetworkEvent.NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND);
}
+ if (VDBG_STALL) {
+ msg.add("consecutive dns timeout count="
+ + mDnsStallDetector.getConsecutiveTimeoutCount());
+ }
}
if (VDBG_STALL) {
- log("isDataStall: result=" + result + ", consecutive dns timeout count="
- + mDnsStallDetector.getConsecutiveTimeoutCount()
- + ", tcp packets received=" + getTcpSocketTracker().getLatestReceivedCount()
- + ", tcp fail rate=" + getTcpSocketTracker().getLatestPacketFailPercentage());
+ log("isDataStall: result=" + result + ", " + msg);
}
return (result == null) ? false : result;
@@ -2298,4 +2309,16 @@ public class NetworkMonitor extends StateMachine {
*/
void log(String s);
}
+
+ @Nullable
+ private static TcpSocketTracker getTcpSocketTrackerOrNull(Context context) {
+ return ((Dependencies.DEFAULT.getDeviceConfigPropertyInt(
+ NAMESPACE_CONNECTIVITY,
+ CONFIG_DATA_STALL_EVALUATION_TYPE,
+ DEFAULT_DATA_STALL_EVALUATION_TYPES)
+ & DATA_STALL_EVALUATION_TYPE_TCP) != 0)
+ ? new TcpSocketTracker(new TcpSocketTracker.Dependencies(context,
+ ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)))
+ : null;
+ }
}
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
index 0ee49c5..1d2bc86 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
@@ -51,11 +51,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -69,7 +71,10 @@ import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.InetAddresses;
+import android.net.InterfaceConfigurationParcel;
import android.net.IpPrefix;
+import android.net.Layer2PacketParcelable;
+import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.NetworkStackIpMemoryStore;
@@ -103,7 +108,6 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.server.NetworkObserverRegistry;
import com.android.server.NetworkStackService.NetworkStackServiceManager;
import com.android.server.connectivity.ipmemorystore.IpMemoryStoreService;
-import com.android.testutils.HandlerUtilsKt;
import org.junit.After;
import org.junit.Before;
@@ -113,6 +117,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
@@ -124,6 +129,7 @@ import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -142,7 +148,6 @@ public class IpClientIntegrationTest {
@Mock private Context mContext;
@Mock private ConnectivityManager mCm;
- @Mock private INetd mMockNetd;
@Mock private Resources mResources;
@Mock private IIpClientCallbacks mCb;
@Mock private AlarmManager mAlarm;
@@ -151,8 +156,9 @@ public class IpClientIntegrationTest {
@Mock private NetworkStackIpMemoryStore mIpMemoryStore;
@Mock private IpMemoryStoreService mIpMemoryStoreService;
+ @Spy private INetd mNetd;
+
private String mIfaceName;
- private INetd mNetd;
private HandlerThread mPacketReaderThread;
private Handler mHandler;
private TapPacketReader mPacketReader;
@@ -249,7 +255,7 @@ public class IpClientIntegrationTest {
@Override
public INetd getNetd(Context context) {
- return mMockNetd;
+ return mNetd;
}
@Override
@@ -310,6 +316,10 @@ public class IpClientIntegrationTest {
setUpIpClient();
}
+ private void awaitIpClientShutdown() throws Exception {
+ verify(mCb, timeout(TEST_TIMEOUT_MS)).onQuit();
+ }
+
@After
public void tearDown() throws Exception {
if (mPacketReader != null) {
@@ -319,6 +329,7 @@ public class IpClientIntegrationTest {
mPacketReaderThread.quitSafely();
}
mIpc.shutdown();
+ awaitIpClientShutdown();
}
private void setUpTapInterface() {
@@ -350,7 +361,7 @@ public class IpClientIntegrationTest {
final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
final IBinder netdIBinder =
(IBinder) inst.getContext().getSystemService(Context.NETD_SERVICE);
- mNetd = INetd.Stub.asInterface(netdIBinder);
+ mNetd = spy(INetd.Stub.asInterface(netdIBinder));
when(mContext.getSystemService(eq(Context.NETD_SERVICE))).thenReturn(netdIBinder);
assertNotNull(mNetd);
@@ -434,18 +445,21 @@ public class IpClientIntegrationTest {
}
private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled,
- final boolean isDhcpRapidCommitEnabled) throws RemoteException {
- ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+ final boolean isDhcpRapidCommitEnabled, final boolean isPreconnectionEnabled)
+ throws RemoteException {
+ ProvisioningConfiguration.Builder builder = new ProvisioningConfiguration.Builder()
.withoutIpReachabilityMonitor()
- .withoutIPv6()
- .build();
+ .withoutIPv6();
+ if (isPreconnectionEnabled) builder.withPreconnection();
mDependencies.setDhcpLeaseCacheEnabled(isDhcpLeaseCacheEnabled);
mDependencies.setDhcpRapidCommitEnabled(isDhcpRapidCommitEnabled);
mIpc.setL2KeyAndGroupHint(TEST_L2KEY, TEST_GROUPHINT);
- mIpc.startProvisioning(config);
+ mIpc.startProvisioning(builder.build());
verify(mCb).setNeighborDiscoveryOffload(true);
- verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(false);
+ if (!isPreconnectionEnabled) {
+ verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(false);
+ }
verify(mCb, never()).onProvisioningFailure(any());
}
@@ -480,7 +494,8 @@ public class IpClientIntegrationTest {
private void performDhcpHandshake(final boolean isSuccessLease,
final Integer leaseTimeSec, final boolean isDhcpLeaseCacheEnabled,
final boolean isDhcpRapidCommitEnabled, final int mtu) throws Exception {
- startIpClientProvisioning(isDhcpLeaseCacheEnabled, isDhcpRapidCommitEnabled);
+ startIpClientProvisioning(isDhcpLeaseCacheEnabled, isDhcpRapidCommitEnabled,
+ false /* isPreconnectionEnabled */);
DhcpPacket packet;
while ((packet = getNextDhcpPacket()) != null) {
@@ -523,7 +538,7 @@ public class IpClientIntegrationTest {
return null;
}).when(mIpMemoryStore).retrieveNetworkAttributes(eq(TEST_L2KEY), any());
startIpClientProvisioning(true /* isDhcpLeaseCacheEnabled */,
- false /* isDhcpRapidCommitEnabled */);
+ false /* isDhcpRapidCommitEnabled */, false /* isPreconnectionEnabled */);
return getNextDhcpPacket();
}
@@ -560,12 +575,12 @@ public class IpClientIntegrationTest {
if (shouldRemoveTapInterface) removeTapInterface(mPacketReader.createFd());
try {
mIpc.shutdown();
- HandlerUtilsKt.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
+ awaitIpClientShutdown();
if (shouldRemoveTapInterface) {
- verify(mMockNetd, never()).interfaceSetMtu(mIfaceName, TEST_DEFAULT_MTU);
+ verify(mNetd, never()).interfaceSetMtu(mIfaceName, TEST_DEFAULT_MTU);
} else {
// Verify that MTU indeed has been restored or not.
- verify(mMockNetd, times(shouldChangeMtu ? 1 : 0))
+ verify(mNetd, times(shouldChangeMtu ? 1 : 0))
.interfaceSetMtu(mIfaceName, TEST_DEFAULT_MTU);
}
verifyAfterIpClientShutdown();
@@ -574,12 +589,58 @@ public class IpClientIntegrationTest {
}
}
+ private void doIpClientProvisioningWithPreconnectionTest(final boolean isDhcpRapidCommitEnabled,
+ final boolean shouldAbortPreconnection) throws Exception {
+ final long currentTime = System.currentTimeMillis();
+ final ArgumentCaptor<List<Layer2PacketParcelable>> l2PacketList =
+ ArgumentCaptor.forClass(List.class);
+ final ArgumentCaptor<InterfaceConfigurationParcel> ifConfig =
+ ArgumentCaptor.forClass(InterfaceConfigurationParcel.class);
+
+ startIpClientProvisioning(true /* isDhcpLeaseCacheEnabled */,
+ isDhcpRapidCommitEnabled, true /* isDhcpPreConnectionEnabled */);
+ verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
+ .onPreconnectionStart(l2PacketList.capture());
+ final byte[] payload = l2PacketList.getValue().get(0).payload;
+ DhcpPacket packet = DhcpPacket.decodeFullPacket(payload, payload.length, ENCAP_L2);
+ assertTrue(packet instanceof DhcpDiscoverPacket);
+
+ if (shouldAbortPreconnection) {
+ mIpc.sendMessage(IpClient.CMD_COMPLETE_PRECONNECTION, 0 /* abort */);
+ packet = getNextDhcpPacket();
+ assertTrue(packet instanceof DhcpDiscoverPacket);
+ }
+
+ final short mtu = (short) TEST_DEFAULT_MTU;
+ if (!isDhcpRapidCommitEnabled) {
+ sendResponse(buildDhcpOfferPacket(packet, TEST_LEASE_DURATION_S, mtu));
+ packet = getNextDhcpPacket();
+ assertTrue(packet instanceof DhcpRequestPacket);
+ }
+ // TODO: currently the DHCPACK packet doesn't include the Rapid Commit option.
+ // This does not matter because the client will accept the ACK even if the Rapid Commit
+ // option is not present. Fix the test code, and then change the client to ensure
+ // it will only accept the ACK if the Rapid Commit option is present.
+ sendResponse(buildDhcpAckPacket(packet, TEST_LEASE_DURATION_S, mtu));
+ if (!shouldAbortPreconnection) {
+ mIpc.sendMessage(IpClient.CMD_COMPLETE_PRECONNECTION, 1 /* success */);
+ }
+ verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(false);
+
+ final LinkAddress ipAddress = new LinkAddress(CLIENT_ADDR, PREFIX_LENGTH);
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetCfg(ifConfig.capture());
+ assertEquals(ifConfig.getValue().ifName, mIfaceName);
+ assertEquals(ifConfig.getValue().ipv4Addr, ipAddress.getAddress().getHostAddress());
+ assertEquals(ifConfig.getValue().prefixLength, PREFIX_LENGTH);
+ assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
+ }
+
@Test
public void testDhcpInit() throws Exception {
startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */,
- false /* isDhcpRapidCommitEnabled */);
+ false /* isDhcpRapidCommitEnabled */, false /* isPreconnectionEnabled */);
final DhcpPacket packet = getNextDhcpPacket();
- assertTrue(DhcpDiscoverPacket.class.isInstance(packet));
+ assertTrue(packet instanceof DhcpDiscoverPacket);
}
@Test
@@ -646,7 +707,7 @@ public class IpClientIntegrationTest {
.setGroupHint(TEST_GROUPHINT)
.setDnsAddresses(Collections.singletonList(SERVER_ADDR))
.build(), false /* timeout */);
- assertTrue(DhcpRequestPacket.class.isInstance(packet));
+ assertTrue(packet instanceof DhcpRequestPacket);
}
@Test
@@ -659,13 +720,13 @@ public class IpClientIntegrationTest {
.setGroupHint(TEST_GROUPHINT)
.setDnsAddresses(Collections.singletonList(SERVER_ADDR))
.build(), false /* timeout */);
- assertTrue(DhcpDiscoverPacket.class.isInstance(packet));
+ assertTrue(packet instanceof DhcpDiscoverPacket);
}
@Test
public void testDhcpClientStartWithNullRetrieveNetworkAttributes() throws Exception {
final DhcpPacket packet = getReplyFromDhcpLease(null /* na */, false /* timeout */);
- assertTrue(DhcpDiscoverPacket.class.isInstance(packet));
+ assertTrue(packet instanceof DhcpDiscoverPacket);
}
@Test
@@ -678,7 +739,7 @@ public class IpClientIntegrationTest {
.setGroupHint(TEST_GROUPHINT)
.setDnsAddresses(Collections.singletonList(SERVER_ADDR))
.build(), true /* timeout */);
- assertTrue(DhcpDiscoverPacket.class.isInstance(packet));
+ assertTrue(packet instanceof DhcpDiscoverPacket);
}
@Test
@@ -689,15 +750,15 @@ public class IpClientIntegrationTest {
.setGroupHint(TEST_GROUPHINT)
.setDnsAddresses(Collections.singletonList(SERVER_ADDR))
.build(), false /* timeout */);
- assertTrue(DhcpDiscoverPacket.class.isInstance(packet));
+ assertTrue(packet instanceof DhcpDiscoverPacket);
}
@Test
public void testDhcpClientRapidCommitEnabled() throws Exception {
startIpClientProvisioning(true /* isDhcpLeaseCacheEnabled */,
- true /* isDhcpRapidCommitEnabled */);
+ true /* isDhcpRapidCommitEnabled */, false /* isPreconnectionEnabled */);
final DhcpPacket packet = getNextDhcpPacket();
- assertTrue(DhcpDiscoverPacket.class.isInstance(packet));
+ assertTrue(packet instanceof DhcpDiscoverPacket);
}
@Test
@@ -712,7 +773,7 @@ public class IpClientIntegrationTest {
@Test
public void testRestoreInitialInterfaceMtu_WithException() throws Exception {
- doThrow(new RemoteException("NetdNativeService::interfaceSetMtu")).when(mMockNetd)
+ doThrow(new RemoteException("NetdNativeService::interfaceSetMtu")).when(mNetd)
.interfaceSetMtu(mIfaceName, TEST_DEFAULT_MTU);
doRestoreInitialMtuTest(true /* shouldChangeMtu */, false /* shouldRemoveTapInterface */);
@@ -913,4 +974,79 @@ public class IpClientIntegrationTest {
verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningFailure(captor.capture());
lp = captor.getValue();
- }}
+ assertNotNull(lp);
+ assertEquals(0, lp.getDnsServers().size());
+ reset(mCb);
+ }
+
+ @Test
+ public void testIpClientClearingIpAddressState() throws Exception {
+ final long currentTime = System.currentTimeMillis();
+ performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
+ true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
+ TEST_DEFAULT_MTU);
+ assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
+
+ ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
+ verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture());
+ LinkProperties lp = captor.getValue();
+ assertNotNull(lp);
+ assertEquals(1, lp.getAddresses().size());
+ assertTrue(lp.getAddresses().contains(InetAddress.getByName(CLIENT_ADDR.getHostAddress())));
+
+ // Stop IpClient and expect a final LinkProperties callback with an empty LP.
+ mIpc.stop();
+ verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(argThat(
+ x -> x.getAddresses().size() == 0
+ && x.getRoutes().size() == 0
+ && x.getDnsServers().size() == 0));
+ reset(mCb);
+
+ // Pretend that something else (e.g., Tethering) used the interface and left an IP address
+ // configured on it. When IpClient starts, it must clear this address before proceeding.
+ // TODO: test IPv6 instead, since the DHCP client will remove this address by replacing it
+ // with the new address.
+ mNetd.interfaceAddAddress(mIfaceName, "192.0.2.99", 26);
+
+ // start IpClient again and should enter Clearing State and wait for the message from kernel
+ performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
+ true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
+ TEST_DEFAULT_MTU);
+
+ verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture());
+ lp = captor.getValue();
+ assertNotNull(lp);
+ assertEquals(1, lp.getAddresses().size());
+ assertTrue(lp.getAddresses().contains(InetAddress.getByName(CLIENT_ADDR.getHostAddress())));
+ }
+
+ @Test
+ public void testDhcpClientPreconnectionAbort() throws Exception {
+ doIpClientProvisioningWithPreconnectionTest(false /* isDhcpRapidCommitEnabled */,
+ true /* shouldAbortPreconnection */);
+ }
+
+ @Test
+ public void testDhcpClientPreconnectionEnabled_WithoutRapidCommit() throws Exception {
+ doIpClientProvisioningWithPreconnectionTest(false /* isDhcpRapidCommitEnabled */,
+ false /* shouldAbortPreconnection */);
+ }
+
+ // So far DHCPACK doesn't include Rapid Commit option(aosp/1092270 is adding the option), when
+ // receiving the DHCPACK packet in DhcpPreconnectionState or DhcpInitState, dropping the DHCPACK
+ // packet directly, which would cause test cases with enabled "isDhcpRapidCommitEnabled" flag
+ // fail.
+ @Ignore
+ @Test
+ public void testDhcpClientPreconnectionEnabled() throws Exception {
+ doIpClientProvisioningWithPreconnectionTest(true /* isDhcpRapidCommitEnabled */,
+ false /* shouldAbortPreconnection */);
+ }
+
+ @Ignore
+ @Test
+ public void testDhcpClientPreconnectionEnabled_WithRapidCommit() throws Exception {
+ doIpClientProvisioningWithPreconnectionTest(true /* isDhcpRapidCommitEnabled */,
+ true /* shouldAbortPreconnection */);
+ }
+}
diff --git a/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java b/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java
index 7079a28..e645a2c 100644
--- a/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java
+++ b/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java
@@ -66,8 +66,9 @@ public class ProvisioningConfigurationTest {
mConfig.mIPv6AddrGenMode = 123;
mConfig.mNetwork = new Network(321);
mConfig.mDisplayName = "test_config";
+ mConfig.mEnablePreconnection = false;
// Any added field must be included in equals() to be tested properly
- assertFieldCountEquals(12, ProvisioningConfiguration.class);
+ assertFieldCountEquals(13, ProvisioningConfiguration.class);
}
@Test
@@ -99,6 +100,12 @@ public class ProvisioningConfigurationTest {
doParcelUnparcelTest();
}
+ @Test
+ public void testParcelUnparcel_WithPreDhcpConnection() {
+ mConfig.mEnablePreconnection = true;
+ doParcelUnparcelTest();
+ }
+
private void doParcelUnparcelTest() {
final ProvisioningConfiguration unparceled =
fromStableParcelable(mConfig.toStableParcelable());
@@ -128,7 +135,8 @@ public class ProvisioningConfigurationTest {
assertNotEqualsAfterChange(c -> c.mNetwork = null);
assertNotEqualsAfterChange(c -> c.mDisplayName = "other_test");
assertNotEqualsAfterChange(c -> c.mDisplayName = null);
- assertFieldCountEquals(12, ProvisioningConfiguration.class);
+ assertNotEqualsAfterChange(c -> c.mEnablePreconnection = true);
+ assertFieldCountEquals(13, ProvisioningConfiguration.class);
}
private void assertNotEqualsAfterChange(Consumer<ProvisioningConfiguration> mutator) {
diff --git a/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java b/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java
index 16cb59a..f65de9c 100644
--- a/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java
+++ b/tests/unit/src/com/android/networkstack/netlink/TcpInfoTest.java
@@ -90,6 +90,13 @@ public class TcpInfoTest {
private static final byte[] TCP_INFO_BYTES =
HexEncoding.decode(TCP_INFO_HEX.toCharArray(), false);
+ private static final String EXPANDED_TCP_INFO_HEX = TCP_INFO_HEX
+ + "00000000" // tcpi_delivered
+ + "00000000"; // tcpi_delivered_ce
+ private static final byte[] EXPANDED_TCP_INFO_BYTES =
+ HexEncoding.decode(EXPANDED_TCP_INFO_HEX.toCharArray(), false);
+ private static final int EXPANDED_TCP_INFO_LENGTH =
+ EXPANDED_TCP_INFO_BYTES.length - TCP_INFO_BYTES.length;
@Test
public void testParseTcpInfo() {
final ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES);
@@ -100,6 +107,23 @@ public class TcpInfoTest {
}
@Test
+ public void testParseTcpInfoExpanded() {
+ final ByteBuffer buffer = ByteBuffer.wrap(EXPANDED_TCP_INFO_BYTES);
+ final Map<TcpInfo.Field, Number> expected = makeTestTcpInfoHash();
+ final TcpInfo parsedInfo =
+ TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1 + EXPANDED_TCP_INFO_LENGTH);
+
+ assertEquals(parsedInfo, new TcpInfo(expected));
+ assertEquals(buffer.limit(), buffer.position());
+
+ // reset the index.
+ buffer.position(0);
+ final TcpInfo parsedInfoShorterLen = TcpInfo.parse(buffer, TCP_INFO_LENGTH_V1);
+ assertEquals(parsedInfoShorterLen, new TcpInfo(expected));
+ assertEquals(TCP_INFO_LENGTH_V1, buffer.position());
+ }
+
+ @Test
public void testValidOffset() {
final ByteBuffer buffer = ByteBuffer.wrap(TCP_INFO_BYTES);
diff --git a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
index 6a92d8a..25ffc66 100644
--- a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
+++ b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
@@ -46,6 +46,7 @@ import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.HashMap;
// TODO: Add more tests for missing coverage.
@@ -55,9 +56,9 @@ public class TcpSocketTrackerTest {
private static final int TEST_BUFFER_SIZE = 1024;
private static final String DIAG_MSG_HEX =
// struct nlmsghdr.
- "00000058" + // length = 88
- "0020" + // type = SOCK_DIAG_BY_FAMILY
- "0103" + // flags = NLM_F_REQUEST | NLM_F_DUMP
+ "58000000" + // length = 88
+ "1400" + // type = SOCK_DIAG_BY_FAMILY
+ "0301" + // flags = NLM_F_REQUEST | NLM_F_DUMP
"00000000" + // seqno
"00000000" + // pid (0 == kernel)
// struct inet_diag_req_v2
@@ -66,12 +67,12 @@ public class TcpSocketTrackerTest {
"00" + // timer
"00" + // retrans
// inet_diag_sockid
- "A5DE" + // idiag_sport = 42462
- "B971" + // idiag_dport = 47473
+ "DEA5" + // idiag_sport = 42462
+ "71B9" + // idiag_dport = 47473
"0a006402000000000000000000000000" + // idiag_src = 10.0.100.2
"08080808000000000000000000000000" + // idiag_dst = 8.8.8.8
"00000000" + // idiag_if
- "000027760000ED34" + // idiag_cookie = 43387759684916
+ "34ED000076270000" + // idiag_cookie = 43387759684916
"00000000" + // idiag_expires
"00000000" + // idiag_rqueue
"00000000" + // idiag_wqueue
@@ -82,9 +83,9 @@ public class TcpSocketTrackerTest {
// Hexadecimal representation of a SOCK_DIAG response with tcp info.
private static final String SOCK_DIAG_TCP_INET_HEX =
// struct nlmsghdr.
- "00000114" + // length = 276
- "0020" + // type = SOCK_DIAG_BY_FAMILY
- "0103" + // flags = NLM_F_REQUEST | NLM_F_DUMP
+ "14010000" + // length = 276
+ "1400" + // type = SOCK_DIAG_BY_FAMILY
+ "0301" + // flags = NLM_F_REQUEST | NLM_F_DUMP
"00000000" + // seqno
"00000000" + // pid (0 == kernel)
// struct inet_diag_req_v2
@@ -93,26 +94,26 @@ public class TcpSocketTrackerTest {
"00" + // timer
"00" + // retrans
// inet_diag_sockid
- "A5DE" + // idiag_sport = 42462
- "B971" + // idiag_dport = 47473
+ "DEA5" + // idiag_sport = 42462
+ "71B9" + // idiag_dport = 47473
"0a006402000000000000000000000000" + // idiag_src = 10.0.100.2
"08080808000000000000000000000000" + // idiag_dst = 8.8.8.8
"00000000" + // idiag_if
- "000027760000ED34" + // idiag_cookie = 43387759684916
+ "34ED000076270000" + // idiag_cookie = 43387759684916
"00000000" + // idiag_expires
"00000000" + // idiag_rqueue
"00000000" + // idiag_wqueue
"00000000" + // idiag_uid
"00000000" + // idiag_inode
// rtattr
- "0005" + // len = 5
- "0008" + // type = 8
+ "0500" + // len = 5
+ "0800" + // type = 8
"00000000" + // data
- "0008" + // len = 8
- "000F" + // type = 15(INET_DIAG_MARK)
- "000C1A85" + // data, socket mark=793221
- "00AC" + // len = 172
- "0002" + // type = 2(INET_DIAG_INFO)
+ "0800" + // len = 8
+ "0F00" + // type = 15(INET_DIAG_MARK)
+ "851A0C00" + // data, socket mark=793221
+ "AC00" + // len = 172
+ "0200" + // type = 2(INET_DIAG_INFO)
// tcp_info
"01" + // state = TCP_ESTABLISHED
"00" + // ca_state = TCP_CA_OPEN
@@ -122,38 +123,38 @@ public class TcpSocketTrackerTest {
"07" + // option = TCPI_OPT_WSCALE|TCPI_OPT_SACK|TCPI_OPT_TIMESTAMPS
"88" + // wscale = 8
"00" + // delivery_rate_app_limited = 0
- "001B914A" + // rto = 1806666
+ "4A911B00" + // rto = 1806666
"00000000" + // ato = 0
- "0000052E" + // sndMss = 1326
- "00000218" + // rcvMss = 536
+ "2E050000" + // sndMss = 1326
+ "18020000" + // rcvMss = 536
"00000000" + // unsacked = 0
"00000000" + // acked = 0
"00000000" + // lost = 0
"00000000" + // retrans = 0
"00000000" + // fackets = 0
- "000000BB" + // lastDataSent = 187
+ "BB000000" + // lastDataSent = 187
"00000000" + // lastAckSent = 0
- "000000BB" + // lastDataRecv = 187
- "000000BB" + // lastDataAckRecv = 187
- "000005DC" + // pmtu = 1500
- "00015630" + // rcvSsthresh = 87600
- "00092C3E" + // rttt = 601150
- "0004961F" + // rttvar = 300575
- "00000578" + // sndSsthresh = 1400
- "0000000A" + // sndCwnd = 10
- "000005A8" + // advmss = 1448
- "00000003" + // reordering = 3
+ "BB000000" + // lastDataRecv = 187
+ "BB000000" + // lastDataAckRecv = 187
+ "DC050000" + // pmtu = 1500
+ "30560100" + // rcvSsthresh = 87600
+ "3E2C0900" + // rttt = 601150
+ "1F960400" + // rttvar = 300575
+ "78050000" + // sndSsthresh = 1400
+ "0A000000" + // sndCwnd = 10
+ "A8050000" + // advmss = 1448
+ "03000000" + // reordering = 3
"00000000" + // rcvrtt = 0
- "00015630" + // rcvspace = 87600
+ "30560100" + // rcvspace = 87600
"00000000" + // totalRetrans = 0
- "000000000000AC53" + // pacingRate = 44115
+ "53AC000000000000" + // pacingRate = 44115
"FFFFFFFFFFFFFFFF" + // maxPacingRate = 18446744073709551615
- "0000000000000001" + // bytesAcked = 1
+ "0100000000000000" + // bytesAcked = 1
"0000000000000000" + // bytesReceived = 0
- "0000000A" + // SegsOut = 10
+ "0A000000" + // SegsOut = 10
"00000000" + // SegsIn = 0
"00000000" + // NotSentBytes = 0
- "00092C3E" + // minRtt = 601150
+ "3E2C0900" + // minRtt = 601150
"00000000" + // DataSegsIn = 0
"00000000" + // DataSegsOut = 0
"0000000000000000"; // deliverRate = 0
@@ -162,9 +163,9 @@ public class TcpSocketTrackerTest {
private static final String TEST_RESPONSE_HEX = SOCK_DIAG_TCP_INET_HEX
// struct nlmsghdr
- + "00000014" // length = 20
- + "0003" // type = NLMSG_DONE
- + "0103" // flags = NLM_F_REQUEST | NLM_F_DUMP
+ + "14000000" // length = 20
+ + "0300" // type = NLMSG_DONE
+ + "0301" // flags = NLM_F_REQUEST | NLM_F_DUMP
+ "00000000" // seqno
+ "00000000" // pid (0 == kernel)
// struct inet_diag_req_v2
@@ -188,9 +189,15 @@ public class TcpSocketTrackerTest {
anyInt())).thenReturn(DEFAULT_TCP_PACKETS_FAIL_PERCENTAGE);
}
+ private ByteBuffer getByteBuffer(final byte[] bytes) {
+ final ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ return buffer;
+ }
+
@Test
public void testParseSockInfo() {
- final ByteBuffer buffer = ByteBuffer.wrap(SOCK_DIAG_TCP_INET_BYTES);
+ final ByteBuffer buffer = getByteBuffer(SOCK_DIAG_TCP_INET_BYTES);
final TcpSocketTracker tst = new TcpSocketTracker(mDependencies);
buffer.position(SOCKDIAG_MSG_HEADER_SIZE);
final TcpSocketTracker.SocketInfo parsed =
@@ -269,20 +276,21 @@ public class TcpSocketTrackerTest {
when(mDependencies.isTcpInfoParsingSupported()).thenReturn(true);
// No enough bytes remain for a valid NlMsg.
final ByteBuffer invalidBuffer = ByteBuffer.allocate(1);
- when(mDependencies.recvMesssage(any())).thenReturn(invalidBuffer);
+ invalidBuffer.order(ByteOrder.LITTLE_ENDIAN);
+ when(mDependencies.recvMessage(any())).thenReturn(invalidBuffer);
assertTrue(tst.pollSocketsInfo());
assertEquals(-1, tst.getLatestPacketFailPercentage());
assertEquals(0, tst.getSentSinceLastRecv());
// Header only.
- final ByteBuffer headerBuffer = ByteBuffer.wrap(SOCK_DIAG_MSG_BYTES);
- when(mDependencies.recvMesssage(any())).thenReturn(headerBuffer);
+ final ByteBuffer headerBuffer = getByteBuffer(SOCK_DIAG_MSG_BYTES);
+ when(mDependencies.recvMessage(any())).thenReturn(headerBuffer);
assertTrue(tst.pollSocketsInfo());
assertEquals(-1, tst.getLatestPacketFailPercentage());
assertEquals(0, tst.getSentSinceLastRecv());
- final ByteBuffer tcpBuffer = ByteBuffer.wrap(TEST_RESPONSE_BYTES);
- when(mDependencies.recvMesssage(any())).thenReturn(tcpBuffer);
+ final ByteBuffer tcpBuffer = getByteBuffer(TEST_RESPONSE_BYTES);
+ when(mDependencies.recvMessage(any())).thenReturn(tcpBuffer);
assertTrue(tst.pollSocketsInfo());
assertEquals(10, tst.getSentSinceLastRecv());
@@ -297,4 +305,50 @@ public class TcpSocketTrackerTest {
tst.mConfigListener.onPropertiesChanged(null /* properties */);
assertTrue(tst.isDataStallSuspected());
}
+
+ private static final String BAD_DIAG_MSG_HEX =
+ // struct nlmsghdr.
+ "00000058" + // length = 1476395008
+ "1400" + // type = SOCK_DIAG_BY_FAMILY
+ "0301" + // flags = NLM_F_REQUEST | NLM_F_DUMP
+ "00000000" + // seqno
+ "00000000" + // pid (0 == kernel)
+ // struct inet_diag_req_v2
+ "02" + // family = AF_INET
+ "06" + // state
+ "00" + // timer
+ "00" + // retrans
+ // inet_diag_sockid
+ "DEA5" + // idiag_sport = 42462
+ "71B9" + // idiag_dport = 47473
+ "0a006402000000000000000000000000" + // idiag_src = 10.0.100.2
+ "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8
+ "00000000" + // idiag_if
+ "34ED000076270000" + // idiag_cookie = 43387759684916
+ "00000000" + // idiag_expires
+ "00000000" + // idiag_rqueue
+ "00000000" + // idiag_wqueue
+ "00000000" + // idiag_uid
+ "00000000"; // idiag_inode
+ private static final byte[] BAD_SOCK_DIAG_MSG_BYTES =
+ HexEncoding.decode(BAD_DIAG_MSG_HEX.toCharArray(), false);
+
+ @Test
+ public void testPollSocketsInfo_BadFormat() throws Exception {
+ final TcpSocketTracker tst = new TcpSocketTracker(mDependencies);
+ ByteBuffer tcpBuffer = getByteBuffer(TEST_RESPONSE_BYTES);
+
+ when(mDependencies.recvMessage(any())).thenReturn(tcpBuffer);
+ assertTrue(tst.pollSocketsInfo());
+ assertEquals(10, tst.getSentSinceLastRecv());
+ assertEquals(50, tst.getLatestPacketFailPercentage());
+
+ tcpBuffer = getByteBuffer(BAD_SOCK_DIAG_MSG_BYTES);
+ when(mDependencies.recvMessage(any())).thenReturn(tcpBuffer);
+ assertTrue(tst.pollSocketsInfo());
+ // Expect no additional packets, so still 10.
+ assertEquals(10, tst.getSentSinceLastRecv());
+ // Expect to reset to 0.
+ assertEquals(0, tst.getLatestPacketFailPercentage());
+ }
}
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index 75491ec..346b646 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -761,6 +761,7 @@ public class NetworkMonitorTest {
wrappedMonitor.sendTcpPollingEvent();
HandlerUtilsKt.waitForIdle(wrappedMonitor.getHandler(), HANDLER_TIMEOUT_MS);
assertFalse(wrappedMonitor.isDataStall());
+
when(mTst.getLatestReceivedCount()).thenReturn(0);
when(mTst.isDataStallSuspected()).thenReturn(true);
// Trigger a tcp event immediately.
@@ -771,6 +772,19 @@ public class NetworkMonitorTest {
}
@Test
+ public void testIsDataStall_DisableTcp() {
+ // Disable tcp detection with only DNS detect. keep the tcp signal but set to no DNS signal.
+ setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS);
+ WrappedNetworkMonitor wrappedMonitor = makeMonitor(METERED_CAPABILITIES);
+ makeDnsSuccessEvent(wrappedMonitor, 1);
+ wrappedMonitor.sendTcpPollingEvent();
+ HandlerUtilsKt.waitForIdle(wrappedMonitor.getHandler(), HANDLER_TIMEOUT_MS);
+ assertFalse(wrappedMonitor.isDataStall());
+ verify(mTst, never()).isDataStallSuspected();
+ verify(mTst, never()).pollSocketsInfo();
+ }
+
+ @Test
public void testBrokenNetworkNotValidated() throws Exception {
setSslException(mHttpsConnection);
setStatus(mHttpConnection, 500);