diff options
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 - // <linux_src>/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 + // <linux_src>/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); |