summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
5 files changed, 438 insertions, 125 deletions
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;
+ }
}