Merge "Update the target SDK version to 30 in the NetworkStackIntegrationTest."
diff --git a/common/moduleutils/src/android/net/ip/ConntrackMonitor.java b/common/moduleutils/src/android/net/ip/ConntrackMonitor.java
index 9189002..6c72984 100644
--- a/common/moduleutils/src/android/net/ip/ConntrackMonitor.java
+++ b/common/moduleutils/src/android/net/ip/ConntrackMonitor.java
@@ -51,6 +51,12 @@
public static final int NF_NETLINK_CONNTRACK_UPDATE = 2;
public static final int NF_NETLINK_CONNTRACK_DESTROY = 4;
+ // The socket receive buffer size in bytes. If too many conntrack messages are sent too
+ // quickly, the conntrack messages can overflow the socket receive buffer. This can happen
+ // if too many connections are disconnected by losing network and so on. Use a large-enough
+ // buffer to avoid the error ENOBUFS while listening to the conntrack messages.
+ private static final int SOCKET_RECV_BUFSIZE = 6 * 1024 * 1024;
+
/**
* A class for describing parsed netfilter conntrack events.
*/
@@ -176,7 +182,7 @@
public ConntrackMonitor(@NonNull Handler h, @NonNull SharedLog log,
@NonNull ConntrackEventConsumer cb) {
super(h, log, TAG, OsConstants.NETLINK_NETFILTER, NF_NETLINK_CONNTRACK_NEW
- | NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY);
+ | NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY, SOCKET_RECV_BUFSIZE);
mConsumer = cb;
}
diff --git a/common/moduleutils/src/android/net/ip/NetlinkMonitor.java b/common/moduleutils/src/android/net/ip/NetlinkMonitor.java
index 3d314f1..2025967 100644
--- a/common/moduleutils/src/android/net/ip/NetlinkMonitor.java
+++ b/common/moduleutils/src/android/net/ip/NetlinkMonitor.java
@@ -21,6 +21,8 @@
import static android.system.OsConstants.AF_NETLINK;
import static android.system.OsConstants.SOCK_DGRAM;
import static android.system.OsConstants.SOCK_NONBLOCK;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_RCVBUF;
import android.annotation.NonNull;
import android.net.netlink.NetlinkErrorMessage;
@@ -56,9 +58,13 @@
protected final String mTag;
private final int mFamily;
private final int mBindGroups;
+ private final int mSockRcvbufSize;
private static final boolean DBG = false;
+ // Default socket receive buffer size. This means the specific buffer size is not set.
+ private static final int DEFAULT_SOCKET_RECV_BUFSIZE = -1;
+
/**
* Constructs a new {@code NetlinkMonitor} instance.
*
@@ -68,14 +74,23 @@
* @param tag The log tag to use for log messages.
* @param family the Netlink socket family to, e.g., {@code NETLINK_ROUTE}.
* @param bindGroups the netlink groups to bind to.
+ * @param sockRcvbufSize the specific socket receive buffer size in bytes. -1 means that don't
+ * set the specific socket receive buffer size in #createFd and use the default value in
+ * /proc/sys/net/core/rmem_default file. See SO_RCVBUF in man-pages/socket.
*/
public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
- int family, int bindGroups) {
+ int family, int bindGroups, int sockRcvbufSize) {
super(h, NetlinkSocket.DEFAULT_RECV_BUFSIZE);
mLog = log.forSubComponent(tag);
mTag = tag;
mFamily = family;
mBindGroups = bindGroups;
+ mSockRcvbufSize = sockRcvbufSize;
+ }
+
+ public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
+ int family, int bindGroups) {
+ this(h, log, tag, family, bindGroups, DEFAULT_SOCKET_RECV_BUFSIZE);
}
@Override
@@ -84,6 +99,9 @@
try {
fd = Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_NONBLOCK, mFamily);
+ if (mSockRcvbufSize != DEFAULT_SOCKET_RECV_BUFSIZE) {
+ Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, mSockRcvbufSize);
+ }
Os.bind(fd, makeNetlinkSocketAddress(0, mBindGroups));
NetlinkSocket.connectToKernel(fd);
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index efe1054..b03d653 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -18,6 +18,7 @@
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable;
+import static android.net.util.NetworkStackUtils.IPCLIENT_DISABLE_ACCEPT_RA_VERSION;
import static android.net.util.NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION;
import static android.net.util.NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION;
import static android.net.util.SocketUtils.makePacketSocketAddress;
@@ -527,7 +528,7 @@
private boolean mMulticastFiltering;
private long mStartTimeMillis;
private MacAddress mCurrentBssid;
- private boolean mHasDisabledIPv6OnProvLoss;
+ private boolean mHasDisabledIpv6OrAcceptRaOnProvLoss;
/**
* Reading the snapshot is an asynchronous operation initiated by invoking
@@ -859,6 +860,33 @@
false /* defaultEnabled */);
}
+ private void setInitialBssid(final ProvisioningConfiguration req) {
+ final ScanResultInfo scanResultInfo = req.mScanResultInfo;
+ mCurrentBssid = null;
+ // http://b/185202634
+ // ScanResultInfo is not populated in some situations.
+ // On S and above, prefer getting the BSSID from the Layer2Info.
+ // On R and below, get the BSSID from the ScanResultInfo and fall back to
+ // getting it from the Layer2Info. This ensures no regressions if any R
+ // devices pass in a null or meaningless BSSID in the Layer2Info.
+ if (!ShimUtils.isAtLeastS() && scanResultInfo != null) {
+ try {
+ mCurrentBssid = MacAddress.fromString(scanResultInfo.getBssid());
+ } catch (IllegalArgumentException e) {
+ Log.wtf(mTag, "Invalid BSSID: " + scanResultInfo.getBssid()
+ + " in provisioning configuration", e);
+ }
+ }
+ if (mCurrentBssid == null && req.mLayer2Info != null) {
+ mCurrentBssid = req.mLayer2Info.mBssid;
+ }
+ }
+
+ private boolean shouldDisableAcceptRaOnProvisioningLoss() {
+ return mDependencies.isFeatureEnabled(mContext, IPCLIENT_DISABLE_ACCEPT_RA_VERSION,
+ true /* defaultEnabled */);
+ }
+
@Override
protected void onQuitting() {
mCallback.onQuit();
@@ -882,17 +910,7 @@
return;
}
- final ScanResultInfo scanResultInfo = req.mScanResultInfo;
- mCurrentBssid = null;
- if (scanResultInfo != null) {
- try {
- mCurrentBssid = MacAddress.fromString(scanResultInfo.getBssid());
- } catch (IllegalArgumentException e) {
- Log.wtf(mTag, "Invalid BSSID: " + scanResultInfo.getBssid()
- + " in provisioning configuration", e);
- }
- }
-
+ setInitialBssid(req);
if (req.mLayer2Info != null) {
mL2Key = req.mLayer2Info.mL2Key;
mCluster = req.mLayer2Info.mCluster;
@@ -1186,6 +1204,21 @@
return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
}
+ private void setIpv6AcceptRa(int acceptRa) {
+ try {
+ mNetd.setProcSysNet(INetd.IPV6, INetd.CONF, mInterfaceParams.name, "accept_ra",
+ Integer.toString(acceptRa));
+ } catch (Exception e) {
+ Log.e(mTag, "Failed to set accept_ra to " + acceptRa);
+ }
+ }
+
+ private void restartIpv6WithAcceptRaDisabled() {
+ mInterfaceCtrl.disableIPv6();
+ setIpv6AcceptRa(0 /* accept_ra */);
+ startIPv6();
+ }
+
// TODO: Investigate folding all this into the existing static function
// LinkProperties.compareProvisioning() or some other single function that
// takes two LinkProperties objects and returns a ProvisioningChange
@@ -1235,7 +1268,7 @@
// Note that we can still be disconnected by IpReachabilityMonitor
// if the IPv6 default gateway (but not the IPv6 DNS servers; see
// accompanying code in IpReachabilityMonitor) is unreachable.
- final boolean ignoreIPv6ProvisioningLoss = mHasDisabledIPv6OnProvLoss
+ final boolean ignoreIPv6ProvisioningLoss = mHasDisabledIpv6OrAcceptRaOnProvLoss
|| (mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker
&& !mCm.shouldAvoidBadWifi());
@@ -1263,18 +1296,31 @@
if (oldLp.hasGlobalIpv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
// Although link properties have lost IPv6 default route in this case, if IPv4 is still
// working with appropriate routes and DNS servers, we can keep the current connection
- // without disconnecting from the network, just disable IPv6 on that given network until
- // to the next provisioning. Disabling IPv6 will result in all IPv6 connectivity torn
- // down and all IPv6 sockets being closed, the non-routable IPv6 DNS servers will be
- // stripped out, so applications will be able to reconnect immediately over IPv4. See
- // b/131781810.
+ // without disconnecting from the network, just disable IPv6 or accept_ra parameter on
+ // that given network until to the next provisioning.
+ //
+ // Disabling IPv6 stack will result in all IPv6 connectivity torn down and all IPv6
+ // sockets being closed, the non-routable IPv6 DNS servers will be stripped out, so
+ // applications will be able to reconnect immediately over IPv4. See b/131781810.
+ //
+ // Sometimes disabling IPv6 stack might introduce other issues(see b/179222860),
+ // instead disabling accept_ra will result in only IPv4 provisioning and IPv6 link
+ // local address left on the interface, so applications will be able to reconnect
+ // immediately over IPv4 and keep IPv6 link-local capable.
if (newLp.isIpv4Provisioned()) {
- mInterfaceCtrl.disableIPv6();
+ if (shouldDisableAcceptRaOnProvisioningLoss()) {
+ restartIpv6WithAcceptRaDisabled();
+ } else {
+ mInterfaceCtrl.disableIPv6();
+ }
mNetworkQuirkMetrics.setEvent(NetworkQuirkEvent.QE_IPV6_PROVISIONING_ROUTER_LOST);
mNetworkQuirkMetrics.statsWrite();
- mHasDisabledIPv6OnProvLoss = true;
+ mHasDisabledIpv6OrAcceptRaOnProvLoss = true;
delta = PROV_CHANGE_STILL_PROVISIONED;
- mLog.log("Disable IPv6 stack completely when the default router has gone");
+ mLog.log(shouldDisableAcceptRaOnProvisioningLoss()
+ ? "Disabled accept_ra parameter "
+ : "Disabled IPv6 stack completely "
+ + "when the IPv6 default router has gone");
} else {
delta = PROV_CHANGE_LOST_PROVISIONING;
}
@@ -1827,7 +1873,8 @@
@Override
public void enter() {
stopAllIP();
- mHasDisabledIPv6OnProvLoss = false;
+ setIpv6AcceptRa(2 /* accept_ra */);
+ mHasDisabledIpv6OrAcceptRaOnProvLoss = false;
mGratuitousNaTargetAddresses.clear();
mLinkObserver.clearInterfaceParams();
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java
index 098ce2e..81d0c08 100755
--- a/src/android/net/util/NetworkStackUtils.java
+++ b/src/android/net/util/NetworkStackUtils.java
@@ -249,6 +249,12 @@
public static final String IPCLIENT_GARP_NA_ROAMING_VERSION =
"ipclient_garp_na_roaming_version";
+ /**
+ * Experiment flag to disable accept_ra parameter when IPv6 provisioning loss happens due to
+ * the default route has gone.
+ */
+ public static final String IPCLIENT_DISABLE_ACCEPT_RA_VERSION = "ipclient_disable_accept_ra";
+
static {
System.loadLibrary("networkstackutilsjni");
}
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
index c5a8b89..c121b3d 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
@@ -782,11 +782,14 @@
private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled,
final boolean shouldReplyRapidCommitAck, final boolean isPreconnectionEnabled,
final boolean isDhcpIpConflictDetectEnabled, final boolean isIPv6OnlyPreferredEnabled,
- final String displayName, final ScanResultInfo scanResultInfo) throws Exception {
+ final String displayName, final ScanResultInfo scanResultInfo,
+ final Layer2Information layer2Info) throws Exception {
ProvisioningConfiguration.Builder prov = new ProvisioningConfiguration.Builder()
.withoutIpReachabilityMonitor()
- .withLayer2Information(new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
- MacAddress.fromString(TEST_DEFAULT_BSSID)))
+ .withLayer2Information(layer2Info == null
+ ? new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+ MacAddress.fromString(TEST_DEFAULT_BSSID))
+ : layer2Info)
.withoutIPv6();
if (isPreconnectionEnabled) prov.withPreconnection();
if (displayName != null) prov.withDisplayName(displayName);
@@ -808,7 +811,7 @@
throws Exception {
startIpClientProvisioning(isDhcpLeaseCacheEnabled, isDhcpRapidCommitEnabled,
isPreconnectionEnabled, isDhcpIpConflictDetectEnabled, isIPv6OnlyPreferredEnabled,
- null /* displayName */, null /* ScanResultInfo */);
+ null /* displayName */, null /* ScanResultInfo */, null /* layer2Info */);
}
private void assertIpMemoryStoreNetworkAttributes(final Integer leaseTimeSec,
@@ -863,10 +866,11 @@
final boolean isDhcpIpConflictDetectEnabled,
final boolean isIPv6OnlyPreferredEnabled,
final String captivePortalApiUrl, final String displayName,
- final ScanResultInfo scanResultInfo) throws Exception {
+ final ScanResultInfo scanResultInfo, final Layer2Information layer2Info)
+ throws Exception {
startIpClientProvisioning(isDhcpLeaseCacheEnabled, shouldReplyRapidCommitAck,
false /* isPreconnectionEnabled */, isDhcpIpConflictDetectEnabled,
- isIPv6OnlyPreferredEnabled, displayName, scanResultInfo);
+ isIPv6OnlyPreferredEnabled, displayName, scanResultInfo, layer2Info);
return handleDhcpPackets(isSuccessLease, leaseTimeSec, shouldReplyRapidCommitAck, mtu,
captivePortalApiUrl);
}
@@ -912,7 +916,8 @@
return performDhcpHandshake(isSuccessLease, leaseTimeSec, isDhcpLeaseCacheEnabled,
isDhcpRapidCommitEnabled, mtu, isDhcpIpConflictDetectEnabled,
false /* isIPv6OnlyPreferredEnabled */,
- null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
+ null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */,
+ null /* layer2Info */);
}
private List<DhcpPacket> performDhcpHandshake() throws Exception {
@@ -921,10 +926,21 @@
TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
}
- private DhcpPacket getNextDhcpPacket() throws ParseException {
- byte[] packet = mDhcpPacketReadHead.getValue().poll(PACKET_TIMEOUT_MS, this::isDhcpPacket);
+ private DhcpPacket getNextDhcpPacket(final long timeout) throws Exception {
+ byte[] packet;
+ while ((packet = mDhcpPacketReadHead.getValue()
+ .poll(timeout, this::isDhcpPacket)) != null) {
+ final DhcpPacket dhcpPacket = DhcpPacket.decodeFullPacket(packet, packet.length,
+ ENCAP_L2);
+ if (dhcpPacket != null) return dhcpPacket;
+ }
+ return null;
+ }
+
+ private DhcpPacket getNextDhcpPacket() throws Exception {
+ final DhcpPacket packet = getNextDhcpPacket(PACKET_TIMEOUT_MS);
assertNotNull("No expected DHCP packet received on interface within timeout", packet);
- return DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L2);
+ return packet;
}
private DhcpPacket getReplyFromDhcpLease(final NetworkAttributes na, boolean timeout)
@@ -2048,9 +2064,8 @@
final List<DhcpPacket> sentPackets = performDhcpHandshake(true /* isSuccessLease */,
TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */,
false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU,
- false /* isDhcpIpConflictDetectEnabled */,
- false /* isIPv6OnlyPreferredEnabled */,
- null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
+ false /* isDhcpIpConflictDetectEnabled */);
+
assertEquals(2, sentPackets.size());
verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertHostname(true, TEST_HOST_NAME, TEST_HOST_NAME_TRANSLITERATION, sentPackets);
@@ -2066,9 +2081,8 @@
final List<DhcpPacket> sentPackets = performDhcpHandshake(true /* isSuccessLease */,
TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */,
false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU,
- false /* isDhcpIpConflictDetectEnabled */,
- false /* isIPv6OnlyPreferredEnabled */,
- null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
+ false /* isDhcpIpConflictDetectEnabled */);
+
assertEquals(2, sentPackets.size());
verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertHostname(false, TEST_HOST_NAME, TEST_HOST_NAME_TRANSLITERATION, sentPackets);
@@ -2084,9 +2098,8 @@
final List<DhcpPacket> sentPackets = performDhcpHandshake(true /* isSuccessLease */,
TEST_LEASE_DURATION_S, true /* isDhcpLeaseCacheEnabled */,
false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU,
- false /* isDhcpIpConflictDetectEnabled */,
- false /* isIPv6OnlyPreferredEnabled */,
- null /* captivePortalApiUrl */, null /* displayName */, null /* scanResultInfo */);
+ false /* isDhcpIpConflictDetectEnabled */);
+
assertEquals(2, sentPackets.size());
verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertHostname(true, null /* hostname */, null /* hostnameAfterTransliteration */,
@@ -2183,7 +2196,8 @@
false /* isDhcpRapidCommitEnabled */, TEST_DEFAULT_MTU,
false /* isDhcpIpConflictDetectEnabled */,
false /* isIPv6OnlyPreferredEnabled */,
- null /* captivePortalApiUrl */, displayName, info /* scanResultInfo */);
+ null /* captivePortalApiUrl */, displayName, info /* scanResultInfo */,
+ null /* layer2Info */);
assertEquals(2, sentPackets.size());
verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
@@ -2270,10 +2284,9 @@
}
private void doDhcpRoamingTest(final boolean hasMismatchedIpAddress, final String displayName,
- final String ssid, final String bssid, final boolean expectRoaming) throws Exception {
+ final MacAddress bssid, final boolean expectRoaming) throws Exception {
long currentTime = System.currentTimeMillis();
- final ScanResultInfo scanResultInfo = (ssid == null || bssid == null)
- ? null : makeScanResultInfo(ssid, bssid);
+ final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER, bssid);
doAnswer(invocation -> {
// we don't rely on the Init-Reboot state to renew previous cached IP lease.
@@ -2290,7 +2303,8 @@
true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */,
false /* isIPv6OnlyPreferredEnabled */,
- null /* captivePortalApiUrl */, displayName, scanResultInfo);
+ null /* captivePortalApiUrl */, displayName, null /* scanResultInfo */,
+ layer2Info);
verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
@@ -2336,37 +2350,31 @@
@Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
public void testDhcpRoaming() throws Exception {
doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
- TEST_DHCP_ROAM_SSID, TEST_DEFAULT_BSSID, true /* expectRoaming */);
+ MacAddress.fromString(TEST_DEFAULT_BSSID), true /* expectRoaming */);
}
@Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
public void testDhcpRoaming_invalidBssid() throws Exception {
doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
- TEST_DHCP_ROAM_SSID, TEST_DHCP_ROAM_BSSID, false /* expectRoaming */);
+ MacAddress.fromString(TEST_DHCP_ROAM_BSSID), false /* expectRoaming */);
}
@Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
- public void testDhcpRoaming_nullScanResultInfo() throws Exception {
+ public void testDhcpRoaming_nullBssid() throws Exception {
doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
- null /* SSID */, null /* BSSID */, false /* expectRoaming */);
- }
-
- @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
- public void testDhcpRoaming_invalidSsid() throws Exception {
- doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
- TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID, false /* expectRoaming */);
+ null /* BSSID */, false /* expectRoaming */);
}
@Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
public void testDhcpRoaming_invalidDisplayName() throws Exception {
doDhcpRoamingTest(false /* hasMismatchedIpAddress */, "\"test-ssid\"" /* display name */,
- TEST_DHCP_ROAM_SSID, TEST_DEFAULT_BSSID, false /* expectRoaming */);
+ MacAddress.fromString(TEST_DEFAULT_BSSID), false /* expectRoaming */);
}
@Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
public void testDhcpRoaming_mismatchedLeasedIpAddress() throws Exception {
doDhcpRoamingTest(true /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
- TEST_DHCP_ROAM_SSID, TEST_DEFAULT_BSSID, true /* expectRoaming */);
+ MacAddress.fromString(TEST_DEFAULT_BSSID), true /* expectRoaming */);
}
private void performDualStackProvisioning() throws Exception {
@@ -2396,12 +2404,15 @@
reset(mCb);
}
- private void doDualStackProvisioning() throws Exception {
+ private void doDualStackProvisioning(boolean shouldDisableAcceptRa) throws Exception {
when(mCm.shouldAvoidBadWifi()).thenReturn(true);
final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
.withoutIpReachabilityMonitor()
.build();
+
+ setFeatureEnabled(NetworkStackUtils.IPCLIENT_DISABLE_ACCEPT_RA_VERSION,
+ shouldDisableAcceptRa);
// Enable rapid commit to accelerate DHCP handshake to shorten test duration,
// not strictly necessary.
setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */,
@@ -2411,9 +2422,9 @@
performDualStackProvisioning();
}
- @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
- public void testIgnoreIpv6ProvisioningLoss() throws Exception {
- doDualStackProvisioning();
+ @Test @SignatureRequiredTest(reason = "signature perms are required due to mocked callabck")
+ public void testIgnoreIpv6ProvisioningLoss_disableIPv6Stack() throws Exception {
+ doDualStackProvisioning(false /* shouldDisableAcceptRa */);
final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
@@ -2442,9 +2453,45 @@
(long) NetworkQuirkEvent.QE_IPV6_PROVISIONING_ROUTER_LOST.ordinal());
}
+ @Test @SignatureRequiredTest(reason = "signature perms are required due to mocked callabck")
+ public void testIgnoreIpv6ProvisioningLoss_disableAcceptRa() throws Exception {
+ doDualStackProvisioning(true /* shouldDisableAcceptRa */);
+
+ final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
+
+ // Send RA with 0-lifetime and wait until all global IPv6 addresses, IPv6-related default
+ // route and DNS servers have been removed, then verify if there is IPv4-only, IPv6 link
+ // local address and route to fe80::/64 info left in the LinkProperties.
+ sendRouterAdvertisementWithZeroLifetime();
+ verify(mCb, timeout(TEST_TIMEOUT_MS).atLeastOnce()).onLinkPropertiesChange(
+ argThat(x -> {
+ // Only IPv4 provisioned and IPv6 link-local address
+ final boolean isIPv6LinkLocalAndIPv4OnlyProvisioned =
+ (x.getLinkAddresses().size() == 2
+ && x.getDnsServers().size() == 1
+ && x.getAddresses().get(0) instanceof Inet4Address
+ && x.getDnsServers().get(0) instanceof Inet4Address);
+
+ if (!isIPv6LinkLocalAndIPv4OnlyProvisioned) return false;
+ lpFuture.complete(x);
+ return true;
+ }));
+ final LinkProperties lp = lpFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertNotNull(lp);
+ assertEquals(lp.getAddresses().get(0), CLIENT_ADDR);
+ assertEquals(lp.getDnsServers().get(0), SERVER_ADDR);
+ assertTrue(lp.getAddresses().get(1).isLinkLocalAddress());
+
+ reset(mCb);
+
+ // Send an RA to verify that global IPv6 addresses won't be configured on the interface.
+ sendBasicRouterAdvertisement(false /* waitForRs */);
+ verify(mCb, timeout(TEST_TIMEOUT_MS).times(0)).onLinkPropertiesChange(any());
+ }
+
@Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required")
public void testDualStackProvisioning() throws Exception {
- doDualStackProvisioning();
+ doDualStackProvisioning(false /* shouldDisableAcceptRa */);
verify(mCb, never()).onProvisioningFailure(any());
}
@@ -2664,7 +2711,7 @@
public void testNoFdLeaks() throws Exception {
// Shut down and restart IpClient once to ensure that any fds that are opened the first
// time it runs do not cause the test to fail.
- doDualStackProvisioning();
+ doDualStackProvisioning(false /* shouldDisableAcceptRa */);
shutdownAndRecreateIpClient();
// Unfortunately we cannot use a large number of iterations as it would make the test run
@@ -2672,7 +2719,7 @@
final int iterations = 10;
final int before = getNumOpenFds();
for (int i = 0; i < iterations; i++) {
- doDualStackProvisioning();
+ doDualStackProvisioning(false /* shouldDisableAcceptRa */);
shutdownAndRecreateIpClient();
// The last time this loop runs, mIpc will be shut down in tearDown.
}
@@ -2860,10 +2907,13 @@
private void startGratuitousArpAndNaAfterRoamingTest(boolean isGratuitousArpNaRoamingEnabled,
boolean hasIpv4, boolean hasIpv6) throws Exception {
+ final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+ MacAddress.fromString(TEST_DEFAULT_BSSID));
final ScanResultInfo scanResultInfo =
makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID);
final ProvisioningConfiguration.Builder prov = new ProvisioningConfiguration.Builder()
.withoutIpReachabilityMonitor()
+ .withLayer2Information(layer2Info)
.withScanResultInfo(scanResultInfo)
.withDisplayName("ssid");
if (!hasIpv4) prov.withoutIPv4();
@@ -2957,4 +3007,119 @@
assertEquals(0, naList.size());
assertEquals(1, arpList.size());
}
+
+ private void doInitialBssidSetupTest(final Layer2Information layer2Info,
+ final ScanResultInfo scanResultInfo) throws Exception {
+ ProvisioningConfiguration.Builder prov = new ProvisioningConfiguration.Builder()
+ .withoutIpReachabilityMonitor()
+ .withLayer2Information(layer2Info)
+ .withScanResultInfo(scanResultInfo)
+ .withDisplayName("\"0001docomo\"")
+ .withoutIPv6();
+
+ setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* shouldReplyRapidCommitAck */,
+ false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
+ startIpClientProvisioning(prov.build());
+
+ handleDhcpPackets(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
+ true /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, null /* serverSentUrl */);
+ verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
+ forceLayer2Roaming();
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testSetInitialBssidFromLayer2Info() throws Exception {
+ final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+ MacAddress.fromString(TEST_DEFAULT_BSSID));
+
+ doInitialBssidSetupTest(layer2Info, null /* scanResultInfo */);
+
+ // Initial BSSID comes from layer2Info, it's different with target roaming bssid,
+ // then verify that DHCPREQUEST packet is sent after roaming.
+ final DhcpPacket packet = getNextDhcpPacket();
+ assertTrue(packet instanceof DhcpRequestPacket);
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testSetInitialBssidFromLayer2Info_NullBssid() throws Exception {
+ final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+ null /* bssid */);
+ final ScanResultInfo scanResultInfo =
+ makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DHCP_ROAM_BSSID);
+
+ doInitialBssidSetupTest(layer2Info, scanResultInfo);
+
+ // Initial BSSID comes from layer2Info, it's null, no DHCPREQUEST packet
+ // will be sent after roaming.
+ final DhcpPacket packet = getNextDhcpPacket(TEST_TIMEOUT_MS);
+ assertNull(packet);
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testSetInitialBssidFromLayer2Info_SameRoamingBssid() throws Exception {
+ final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+ MacAddress.fromString(TEST_DHCP_ROAM_BSSID));
+
+ doInitialBssidSetupTest(layer2Info, null /* scanResultInfo */);
+
+ // Initial BSSID comes from layer2Info, it's same with target roaming bssid,
+ // no DHCPREQUEST packet will be sent after roaming.
+ final DhcpPacket packet = getNextDhcpPacket(TEST_TIMEOUT_MS);
+ assertNull(packet);
+ }
+
+ @Test @IgnoreAfter(Build.VERSION_CODES.R)
+ public void testSetInitialBssidFromScanResultInfo() throws Exception {
+ final ScanResultInfo scanResultInfo =
+ makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID);
+
+ doInitialBssidSetupTest(null /* layer2Info */, scanResultInfo);
+
+ // Initial BSSID comes from ScanResultInfo, it's different with target roaming bssid,
+ // then verify that DHCPREQUEST packet is sent after roaming.
+ final DhcpPacket packet = getNextDhcpPacket();
+ assertTrue(packet instanceof DhcpRequestPacket);
+ }
+
+ @Test @IgnoreAfter(Build.VERSION_CODES.R)
+ public void testSetInitialBssidFromScanResultInfo_SameRoamingBssid() throws Exception {
+ final ScanResultInfo scanResultInfo =
+ makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DHCP_ROAM_BSSID);
+
+ doInitialBssidSetupTest(null /* layer2Info */, scanResultInfo);
+
+ // Initial BSSID comes from ScanResultInfo, it's same with target roaming bssid,
+ // no DHCPREQUEST packet will be sent after roaming.
+ final DhcpPacket packet = getNextDhcpPacket(TEST_TIMEOUT_MS);
+ assertNull(packet);
+ }
+
+ @Test @IgnoreAfter(Build.VERSION_CODES.R)
+ public void testSetInitialBssidFromScanResultInfo_BrokenInitialBssid() throws Exception {
+ final ScanResultInfo scanResultInfo =
+ makeScanResultInfo(TEST_DEFAULT_SSID, "00:11:22:33:44:");
+
+ doInitialBssidSetupTest(null /* layer2Info */, scanResultInfo);
+
+ // Initial BSSID comes from ScanResultInfo, it's broken MAC address format and fallback
+ // to null layer2Info, no DHCPREQUEST packet will be sent after roaming.
+ final DhcpPacket packet = getNextDhcpPacket(TEST_TIMEOUT_MS);
+ assertNull(packet);
+ }
+
+ @Test @IgnoreAfter(Build.VERSION_CODES.R)
+ public void testSetInitialBssidFromScanResultInfo_BrokenInitialBssidFallback()
+ throws Exception {
+ final Layer2Information layer2Info = new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
+ MacAddress.fromString(TEST_DEFAULT_BSSID));
+ final ScanResultInfo scanResultInfo =
+ makeScanResultInfo(TEST_DEFAULT_SSID, "00:11:22:33:44:");
+
+ doInitialBssidSetupTest(layer2Info, scanResultInfo);
+
+ // Initial BSSID comes from ScanResultInfo, it's broken MAC address format and fallback
+ // to check layer2Info, then verify DHCPREQUEST packet will be sent after roaming.
+ final DhcpPacket packet = getNextDhcpPacket();
+ assertTrue(packet instanceof DhcpRequestPacket);
+ }
}