summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXiao Ma <xiaom@google.com>2019-07-19 10:39:06 +0900
committerXiao Ma <xiaom@google.com>2019-12-04 17:28:33 +0900
commit4a8d1d42b52223cb81ecacb60cc578aeb4452fd5 (patch)
tree11bc7944d63afbb5f61665c05caa239c4ec03aad
parent0656b1be55e323126ab267fe4dcfdbc6fa03ab05 (diff)
Add DHCP Rapid Commit FILS support.
Bug: 140223017 Test: atest FrameworksNetTests NetworkStackTests Test: atest NetworkStackIntegrationTests Test: manual Change-Id: Ibf200891c9742825a599a21b43f02927869f98ab
-rw-r--r--common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java14
-rw-r--r--common/networkstackclient/Android.bp1
-rw-r--r--common/networkstackclient/src/android/net/Layer2PacketParcelable.aidl24
-rw-r--r--common/networkstackclient/src/android/net/ProvisioningConfigurationParcelable.aidl1
-rw-r--r--common/networkstackclient/src/android/net/ip/IIpClient.aidl1
-rw-r--r--common/networkstackclient/src/android/net/ip/IIpClientCallbacks.aidl6
-rw-r--r--src/android/net/dhcp/DhcpClient.java204
-rw-r--r--src/android/net/ip/IpClient.java142
-rw-r--r--tests/integration/src/android/net/ip/IpClientIntegrationTest.java118
-rw-r--r--tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java12
10 files changed, 453 insertions, 70 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 669dbfd..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,6 +363,7 @@ 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
@@ -345,6 +403,7 @@ public class IpClient extends StateMachine {
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;
@@ -607,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() {
@@ -622,6 +686,7 @@ public class IpClient extends StateMachine {
// CHECKSTYLE:OFF IndentationCheck
addState(mStoppedState);
addState(mStartedState);
+ addState(mPreconnectingState, mStartedState);
addState(mClearingIpAddressesState, mStartedState);
addState(mRunningState, mStartedState);
addState(mStoppingState);
@@ -767,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) {
@@ -1229,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;
@@ -1439,6 +1512,25 @@ public class IpClient extends StateMachine {
}
}
+ 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() {
@@ -1456,7 +1548,7 @@ public class IpClient extends StateMachine {
public boolean processMessage(Message msg) {
switch (msg.what) {
case CMD_ADDRESSES_CLEARED:
- transitionTo(mRunningState);
+ transitionTo(isUsingPreconnection() ? mPreconnectingState : mRunningState);
break;
case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
@@ -1487,6 +1579,42 @@ public class IpClient extends StateMachine {
}
}
+ class PreconnectingState extends State {
+ @Override
+ public void enter() {
+ startDhcpClient();
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ 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 DhcpClient.CMD_START_PRECONNECTION:
+ final Layer2PacketParcelable l2Packet = (Layer2PacketParcelable) msg.obj;
+ mCallback.onPreconnectionStart(Collections.singletonList(l2Packet));
+ break;
+
+ 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() {
@@ -1566,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/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
index b1efa71..1d2bc86 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
@@ -71,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;
@@ -126,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;
@@ -441,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());
}
@@ -487,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) {
@@ -530,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();
}
@@ -581,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
@@ -653,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
@@ -666,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
@@ -685,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
@@ -696,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
@@ -965,4 +1019,34 @@ public class IpClientIntegrationTest {
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) {