Merge "Update import class from TrafficStatsConstants to NetworkStackConstants"
diff --git a/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java
index 9e27666..8719e83 100644
--- a/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java
+++ b/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java
@@ -51,7 +51,7 @@
}
@Override
- public String getVenueFriendlyName() {
+ public CharSequence getVenueFriendlyName() {
// Not supported in API level 29
return null;
}
diff --git a/apishim/29/com/android/networkstack/apishim/api29/NetworkRequestShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/NetworkRequestShimImpl.java
new file mode 100644
index 0000000..0c1d837
--- /dev/null
+++ b/apishim/29/com/android/networkstack/apishim/api29/NetworkRequestShimImpl.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 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 permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim.api29;
+
+import android.net.NetworkRequest;
+import android.util.Range;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.networkstack.apishim.common.NetworkRequestShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+
+import java.util.Set;
+
+/**
+ * Implementation of {@link NetworkRequestShim} for API 29.
+ */
+public class NetworkRequestShimImpl implements NetworkRequestShim {
+ protected NetworkRequestShimImpl() {}
+
+ /**
+ * Get a new instance of {@link NetworkRequestShim}.
+ */
+ public static NetworkRequestShim newInstance() {
+ return new NetworkRequestShimImpl();
+ }
+
+ @Override
+ public void setUids(@NonNull NetworkRequest.Builder builder,
+ @Nullable Set<Range<Integer>> uids) throws UnsupportedApiLevelException {
+ // Not supported before API 31.
+ throw new UnsupportedApiLevelException("Not supported before API 31.");
+ }
+}
diff --git a/apishim/30/com/android/networkstack/apishim/api30/NetworkRequestShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/NetworkRequestShimImpl.java
new file mode 100644
index 0000000..b65a556
--- /dev/null
+++ b/apishim/30/com/android/networkstack/apishim/api30/NetworkRequestShimImpl.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 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 permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim.api30;
+
+import android.os.Build;
+
+import com.android.networkstack.apishim.common.NetworkRequestShim;
+import com.android.networkstack.apishim.common.ShimUtils;
+
+/**
+ * Implementation of {@link NetworkRequestShim} for API 30.
+ */
+public class NetworkRequestShimImpl
+ extends com.android.networkstack.apishim.api29.NetworkRequestShimImpl {
+ protected NetworkRequestShimImpl() {
+ super();
+ }
+
+ /**
+ * Get a new instance of {@link NetworkRequestShim}.
+ */
+ public static NetworkRequestShim newInstance() {
+ if (!ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) {
+ return com.android.networkstack.apishim.api29.NetworkRequestShimImpl
+ .newInstance();
+ }
+ return new NetworkRequestShimImpl();
+ }
+}
diff --git a/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java b/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
index 6e3eb19..5af7412 100644
--- a/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
+++ b/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
@@ -33,7 +33,7 @@
}
@Override
- public String getVenueFriendlyName() {
+ public CharSequence getVenueFriendlyName() {
return mData.getVenueFriendlyName();
}
diff --git a/apishim/31/com/android/networkstack/apishim/NetworkRequestShimImpl.java b/apishim/31/com/android/networkstack/apishim/NetworkRequestShimImpl.java
new file mode 100644
index 0000000..b0d5827
--- /dev/null
+++ b/apishim/31/com/android/networkstack/apishim/NetworkRequestShimImpl.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 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 permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim;
+
+import android.net.NetworkRequest;
+import android.os.Build;
+import android.util.Range;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.networkstack.apishim.common.NetworkRequestShim;
+import com.android.networkstack.apishim.common.ShimUtils;
+
+import java.util.Set;
+
+/**
+ * Implementation of {@link NetworkRequestShim} for API 31.
+ */
+public class NetworkRequestShimImpl
+ extends com.android.networkstack.apishim.api30.NetworkRequestShimImpl {
+ protected NetworkRequestShimImpl() {
+ super();
+ }
+
+ /**
+ * Get a new instance of {@link NetworkRequestShim}.
+ */
+ public static NetworkRequestShim newInstance() {
+ if (!ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.R)) {
+ return com.android.networkstack.apishim.api30.NetworkRequestShimImpl
+ .newInstance();
+ }
+ return new NetworkRequestShimImpl();
+ }
+
+ @Override
+ public void setUids(@NonNull NetworkRequest.Builder builder,
+ @Nullable Set<Range<Integer>> uids) {
+ builder.setUids(uids);
+ }
+}
diff --git a/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java b/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
index 26ac9d4..13bf257 100644
--- a/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
+++ b/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
@@ -54,7 +54,7 @@
/**
* @see CaptivePortalData#getVenueFriendlyName()
*/
- String getVenueFriendlyName();
+ CharSequence getVenueFriendlyName();
/**
* @see CaptivePortalData#getUserPortalUrlSource()
diff --git a/apishim/common/com/android/networkstack/apishim/common/NetworkRequestShim.java b/apishim/common/com/android/networkstack/apishim/common/NetworkRequestShim.java
new file mode 100644
index 0000000..7b53efa
--- /dev/null
+++ b/apishim/common/com/android/networkstack/apishim/common/NetworkRequestShim.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 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 permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.apishim.common;
+
+import android.net.NetworkRequest;
+import android.util.Range;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Set;
+
+/**
+ * Interface used to access API methods in {@link android.net.NetworkRequest}, with
+ * appropriate fallbacks if the methods are not yet part of the released API.
+ */
+public interface NetworkRequestShim {
+ /**
+ * See android.net.NetworkRequest.Builder#setUids.
+ * Set the {@code uids} into {@code builder}.
+ */
+ void setUids(@NonNull NetworkRequest.Builder builder,
+ @Nullable Set<Range<Integer>> uids) throws UnsupportedApiLevelException;
+}
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index a73e997..980c6d6 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -18,8 +18,14 @@
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable;
+import static android.net.util.NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
+import static android.system.OsConstants.AF_PACKET;
+import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.SOCK_NONBLOCK;
+import static android.system.OsConstants.SOCK_RAW;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
import static com.android.net.module.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID;
import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission;
@@ -52,6 +58,7 @@
import android.net.shared.ProvisioningConfiguration.ScanResultInfo;
import android.net.shared.ProvisioningConfiguration.ScanResultInfo.InformationElement;
import android.net.util.InterfaceParams;
+import android.net.util.NetworkStackUtils;
import android.net.util.SharedLog;
import android.os.Build;
import android.os.ConditionVariable;
@@ -61,6 +68,8 @@
import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.stats.connectivity.DisconnectCode;
+import android.system.ErrnoException;
+import android.system.Os;
import android.text.TextUtils;
import android.util.LocalLog;
import android.util.Log;
@@ -79,16 +88,21 @@
import com.android.internal.util.WakeupMessage;
import com.android.net.module.util.DeviceConfigUtils;
import com.android.networkstack.apishim.NetworkInformationShimImpl;
+import com.android.networkstack.apishim.SocketUtilsShimImpl;
import com.android.networkstack.apishim.common.NetworkInformationShim;
import com.android.networkstack.apishim.common.ShimUtils;
import com.android.networkstack.metrics.IpProvisioningMetrics;
+import com.android.networkstack.packets.NeighborAdvertisement;
import com.android.server.NetworkObserverRegistry;
import com.android.server.NetworkStackService.NetworkStackServiceManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.MalformedURLException;
+import java.net.SocketAddress;
+import java.net.SocketException;
import java.net.URL;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
@@ -479,6 +493,8 @@
private final MessageHandlingLogger mMsgStateLogger;
private final IpConnectivityLog mMetricsLog;
private final InterfaceController mInterfaceCtrl;
+ // Set of IPv6 addresses for which unsolicited gratuitous NA packets have been sent.
+ private final Set<Inet6Address> mGratuitousNaTargetAddresses = new HashSet<>();
// Ignore nonzero RDNSS option lifetimes below this value. 0 = disabled.
private final int mMinRdnssLifetimeSec;
@@ -564,6 +580,16 @@
public IpConnectivityLog getIpConnectivityLog() {
return new IpConnectivityLog();
}
+
+ /**
+ * Return whether a feature guarded by a feature flag is enabled.
+ * @see NetworkStackUtils#isFeatureEnabled(Context, String, String)
+ */
+ public boolean isFeatureEnabled(final Context context, final String name,
+ boolean defaultEnabled) {
+ return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name,
+ defaultEnabled);
+ }
}
public IpClient(Context context, String ifName, IIpClientCallbacks callback,
@@ -647,6 +673,21 @@
logMsg(msg);
}
+ @Override
+ public void onInterfaceAddressRemoved(LinkAddress address, String iface) {
+ super.onInterfaceAddressRemoved(address, iface);
+ if (!mInterfaceName.equals(iface)) return;
+ if (!address.isIpv6()) return;
+ final Inet6Address targetIp = (Inet6Address) address.getAddress();
+ if (mGratuitousNaTargetAddresses.contains(targetIp)) {
+ mGratuitousNaTargetAddresses.remove(targetIp);
+
+ final String msg = "Global IPv6 address: " + targetIp
+ + " has removed from the set of gratuitous NA target address.";
+ logMsg(msg);
+ }
+ }
+
private void logMsg(String msg) {
Log.d(mTag, msg);
getHandler().post(() -> mLog.log("OBSERVED " + msg));
@@ -793,6 +834,11 @@
mLinkObserver.shutdown();
}
+ private boolean isGratuitousNaEnabled() {
+ return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GRATUITOUS_NA_VERSION,
+ false /* defaultEnabled */);
+ }
+
@Override
protected void onQuitting() {
mCallback.onQuit();
@@ -1377,6 +1423,57 @@
}
}
+ private void sendGratuitousNA(final Inet6Address srcIp, final Inet6Address target) {
+ final int flags = 0; // R=0, S=0, O=0
+ final Inet6Address dstIp = IPV6_ADDR_ALL_ROUTERS_MULTICAST;
+ // Ethernet multicast destination address: 33:33:00:00:00:02.
+ final MacAddress dstMac = NetworkStackUtils.ipv6MulticastToEthernetMulticast(dstIp);
+ final ByteBuffer packet = NeighborAdvertisement.build(mInterfaceParams.macAddr, dstMac,
+ srcIp, dstIp, flags, target);
+ FileDescriptor sock = null;
+ try {
+ sock = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0 /* protocol */);
+ final SocketAddress sockAddress =
+ SocketUtilsShimImpl.newInstance().makePacketSocketAddress(ETH_P_IPV6,
+ mInterfaceParams.index, dstMac.toByteArray());
+ Os.sendto(sock, packet.array(), 0 /* byteOffset */, packet.limit() /* byteCount */,
+ 0 /* flags */, sockAddress);
+ } catch (SocketException | ErrnoException e) {
+ logError("Fail to send Gratuitous Neighbor Advertisement", e);
+ } finally {
+ NetworkStackUtils.closeSocketQuietly(sock);
+ }
+ }
+
+ private Inet6Address getIpv6LinkLocalAddress(final LinkProperties newLp) {
+ Inet6Address src = null;
+ for (LinkAddress la : newLp.getLinkAddresses()) {
+ if (!la.isIpv6()) continue;
+ final Inet6Address ip = (Inet6Address) la.getAddress();
+ if (ip.isLinkLocalAddress()) return ip;
+ }
+ return null;
+ }
+
+ private void maybeSendGratuitousNAs(final LinkProperties newLp) {
+ if (!newLp.hasGlobalIpv6Address() || !isGratuitousNaEnabled()) return;
+
+ final Inet6Address src = getIpv6LinkLocalAddress(newLp);
+ if (src == null) return;
+ for (LinkAddress la : newLp.getLinkAddresses()) {
+ if (!la.isIpv6() || !la.isGlobalPreferred()) continue;
+ final Inet6Address targetIp = (Inet6Address) la.getAddress();
+ // Already sent gratuitous NA with this target global IPv6 address.
+ if (mGratuitousNaTargetAddresses.contains(targetIp)) continue;
+ if (DBG) {
+ Log.d(mTag, "send Gratuitous NA from " + src + ", target Address is "
+ + targetIp);
+ }
+ sendGratuitousNA(src, targetIp);
+ mGratuitousNaTargetAddresses.add(targetIp);
+ }
+ }
+
// Returns false if we have lost provisioning, true otherwise.
private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
final LinkProperties newLp = assembleLinkProperties();
@@ -1384,6 +1481,11 @@
return true;
}
+ // Check if new assigned IPv6 GUA is available in the LinkProperties now. If so, initiate
+ // gratuitous multicast unsolicited Neighbor Advertisements as soon as possible to inform
+ // first-hop routers that the new GUA host is goning to use.
+ maybeSendGratuitousNAs(newLp);
+
// Either success IPv4 or IPv6 provisioning triggers new LinkProperties update,
// wait for the provisioning completion and record the latency.
mIpProvisioningMetrics.setIPv4ProvisionedLatencyOnFirstTime(newLp.isIpv4Provisioned());
@@ -1662,6 +1764,7 @@
public void enter() {
stopAllIP();
mHasDisabledIPv6OnProvLoss = false;
+ mGratuitousNaTargetAddresses.clear();
mLinkObserver.clearInterfaceParams();
resetLinkProperties();
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java
index 5c11733..6e32833 100755
--- a/src/android/net/util/NetworkStackUtils.java
+++ b/src/android/net/util/NetworkStackUtils.java
@@ -17,12 +17,16 @@
package android.net.util;
import android.content.Context;
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
import com.android.net.module.util.DeviceConfigUtils;
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.SocketException;
/**
@@ -232,6 +236,12 @@
*/
public static final String VALIDATION_METRICS_VERSION = "validation_metrics_version";
+ /**
+ * Experiment flag to enable sending gratuitous multicast unsolicited Neighbor Advertisements
+ * to propagate new assigned IPv6 GUA as quickly as possible.
+ */
+ public static final String IPCLIENT_GRATUITOUS_NA_VERSION = "ipclient_gratuitous_na_version";
+
static {
System.loadLibrary("networkstackutilsjni");
}
@@ -247,6 +257,21 @@
}
/**
+ * Convert IPv6 multicast address to ethernet multicast address in network order.
+ */
+ public static MacAddress ipv6MulticastToEthernetMulticast(@NonNull final Inet6Address addr) {
+ final byte[] etherMulticast = new byte[6];
+ final byte[] ipv6Multicast = addr.getAddress();
+ etherMulticast[0] = (byte) 0x33;
+ etherMulticast[1] = (byte) 0x33;
+ etherMulticast[2] = ipv6Multicast[12];
+ etherMulticast[3] = ipv6Multicast[13];
+ etherMulticast[4] = ipv6Multicast[14];
+ etherMulticast[5] = ipv6Multicast[15];
+ return MacAddress.fromBytes(etherMulticast);
+ }
+
+ /**
* Attaches a socket filter that accepts DHCP packets to the given socket.
*/
public static native void attachDhcpFilter(FileDescriptor fd) throws SocketException;
diff --git a/src/com/android/networkstack/NetworkStackNotifier.java b/src/com/android/networkstack/NetworkStackNotifier.java
index 0558d3a..acf3c95 100644
--- a/src/com/android/networkstack/NetworkStackNotifier.java
+++ b/src/com/android/networkstack/NetworkStackNotifier.java
@@ -237,8 +237,8 @@
// If the venue friendly name is available (in Passpoint use-case), display it.
// Otherwise, display the SSID.
- final String friendlyName = capportData.getVenueFriendlyName();
- final String venueDisplayName = TextUtils.isEmpty(friendlyName)
+ final CharSequence friendlyName = capportData.getVenueFriendlyName();
+ final CharSequence venueDisplayName = TextUtils.isEmpty(friendlyName)
? getSsid(networkStatus) : friendlyName;
builder = getNotificationBuilder(channel, networkStatus, res, venueDisplayName)
@@ -284,9 +284,9 @@
private Notification.Builder getNotificationBuilder(@NonNull String channelId,
@NonNull TrackedNetworkStatus networkStatus, @NonNull Resources res,
- @NonNull String ssid) {
+ @NonNull CharSequence networkIdentifier) {
return new Notification.Builder(mContext, channelId)
- .setContentTitle(ssid)
+ .setContentTitle(networkIdentifier)
.setSmallIcon(R.drawable.icon_wifi);
}
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.kt b/tests/integration/src/android/net/ip/IpClientIntegrationTest.kt
index 9f29a2e..eb0a799 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.kt
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.kt
@@ -17,6 +17,7 @@
package android.net.ip
import android.net.ipmemorystore.NetworkAttributes
+import android.util.ArrayMap
import org.mockito.Mockito.any
import org.mockito.ArgumentCaptor
import org.mockito.Mockito.eq
@@ -28,22 +29,20 @@
* Tests for IpClient, run with signature permissions.
*/
class IpClientIntegrationTest : IpClientIntegrationTestCommon() {
+ private val mEnabledFeatures = ArrayMap<String, Boolean>()
+
override fun makeIIpClient(ifaceName: String, cb: IIpClientCallbacks): IIpClient {
return mIpc.makeConnector()
}
override fun useNetworkStackSignature() = true
- override fun setDhcpFeatures(
- isDhcpLeaseCacheEnabled: Boolean,
- isRapidCommitEnabled: Boolean,
- isDhcpIpConflictDetectEnabled: Boolean,
- isIPv6OnlyPreferredEnabled: Boolean
- ) {
- mDependencies.setDhcpLeaseCacheEnabled(isDhcpLeaseCacheEnabled)
- mDependencies.setDhcpRapidCommitEnabled(isRapidCommitEnabled)
- mDependencies.setDhcpIpConflictDetectEnabled(isDhcpIpConflictDetectEnabled)
- mDependencies.setIPv6OnlyPreferredEnabled(isIPv6OnlyPreferredEnabled)
+ override fun isFeatureEnabled(name: String, defaultEnabled: Boolean): Boolean {
+ return mEnabledFeatures.get(name) ?: defaultEnabled
+ }
+
+ override fun setFeatureEnabled(name: String, enabled: Boolean) {
+ mEnabledFeatures.put(name, enabled)
}
override fun getStoredNetworkAttributes(l2Key: String, timeout: Long): NetworkAttributes {
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
index 451437e..ae68362 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
@@ -39,11 +39,15 @@
import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY;
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS;
+import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK;
import static junit.framework.Assert.fail;
@@ -141,6 +145,7 @@
import com.android.networkstack.apishim.common.ShimUtils;
import com.android.networkstack.arp.ArpPacket;
import com.android.networkstack.metrics.IpProvisioningMetrics;
+import com.android.networkstack.packets.NeighborAdvertisement;
import com.android.server.NetworkObserver;
import com.android.server.NetworkObserverRegistry;
import com.android.server.NetworkStackService.NetworkStackServiceManager;
@@ -343,32 +348,12 @@
};
protected class Dependencies extends IpClient.Dependencies {
- private boolean mIsDhcpLeaseCacheEnabled;
- private boolean mIsDhcpRapidCommitEnabled;
- private boolean mIsDhcpIpConflictDetectEnabled;
// Can't use SparseIntArray, it doesn't have an easy way to know if a key is not present.
private HashMap<String, Integer> mIntConfigProperties = new HashMap<>();
private DhcpClient mDhcpClient;
private boolean mIsHostnameConfigurationEnabled;
private String mHostname;
private boolean mIsInterfaceRecovered;
- private boolean mIsIPv6OnlyPreferredEnabled;
-
- public void setDhcpLeaseCacheEnabled(final boolean enable) {
- mIsDhcpLeaseCacheEnabled = enable;
- }
-
- public void setDhcpRapidCommitEnabled(final boolean enable) {
- mIsDhcpRapidCommitEnabled = enable;
- }
-
- public void setDhcpIpConflictDetectEnabled(final boolean enable) {
- mIsDhcpIpConflictDetectEnabled = enable;
- }
-
- public void setIPv6OnlyPreferredEnabled(final boolean enable) {
- mIsIPv6OnlyPreferredEnabled = enable;
- }
public void setHostnameConfiguration(final boolean enable, final String hostname) {
mIsHostnameConfigurationEnabled = enable;
@@ -411,25 +396,19 @@
}
@Override
+ public boolean isFeatureEnabled(final Context context, final String name,
+ final boolean defaultEnabled) {
+ return IpClientIntegrationTestCommon.this.isFeatureEnabled(name, defaultEnabled);
+ }
+
+ @Override
public DhcpClient.Dependencies getDhcpClientDependencies(
NetworkStackIpMemoryStore ipMemoryStore, IpProvisioningMetrics metrics) {
return new DhcpClient.Dependencies(ipMemoryStore, metrics) {
@Override
public boolean isFeatureEnabled(final Context context, final String name,
final boolean defaultEnabled) {
- switch (name) {
- case NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION:
- return mIsDhcpRapidCommitEnabled;
- case NetworkStackUtils.DHCP_INIT_REBOOT_VERSION:
- return mIsDhcpLeaseCacheEnabled;
- case NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION:
- return mIsDhcpIpConflictDetectEnabled;
- case NetworkStackUtils.DHCP_IPV6_ONLY_PREFERRED_VERSION:
- return mIsIPv6OnlyPreferredEnabled;
- default:
- fail("Invalid experiment flag: " + name);
- return false;
- }
+ return Dependencies.this.isFeatureEnabled(context, name, defaultEnabled);
}
@Override
@@ -473,9 +452,9 @@
protected abstract IIpClient makeIIpClient(
@NonNull String ifaceName, @NonNull IIpClientCallbacks cb);
- protected abstract void setDhcpFeatures(boolean isDhcpLeaseCacheEnabled,
- boolean isRapidCommitEnabled, boolean isDhcpIpConflictDetectEnabled,
- boolean isIPv6OnlyPreferredEnabled);
+ protected abstract void setFeatureEnabled(String name, boolean enabled);
+
+ protected abstract boolean isFeatureEnabled(String name, boolean defaultEnabled);
protected abstract boolean useNetworkStackSignature();
@@ -490,6 +469,17 @@
&& (mIsSignatureRequiredTest || !TestNetworkStackServiceClient.isSupported());
}
+ protected void setDhcpFeatures(final boolean isDhcpLeaseCacheEnabled,
+ final boolean isRapidCommitEnabled, final boolean isDhcpIpConflictDetectEnabled,
+ final boolean isIPv6OnlyPreferredEnabled) {
+ setFeatureEnabled(NetworkStackUtils.DHCP_INIT_REBOOT_VERSION, isDhcpLeaseCacheEnabled);
+ setFeatureEnabled(NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION, isRapidCommitEnabled);
+ setFeatureEnabled(NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION,
+ isDhcpIpConflictDetectEnabled);
+ setFeatureEnabled(NetworkStackUtils.DHCP_IPV6_ONLY_PREFERRED_VERSION,
+ isIPv6OnlyPreferredEnabled);
+ }
+
@Before
public void setUp() throws Exception {
final Method testMethod = IpClientIntegrationTestCommon.class.getMethod(
@@ -704,6 +694,14 @@
}
}
+ private NeighborAdvertisement parseNeighborAdvertisementOrNull(final byte[] packet) {
+ try {
+ return NeighborAdvertisement.parse(packet, packet.length);
+ } catch (NeighborAdvertisement.ParseException e) {
+ return null;
+ }
+ }
+
private static ByteBuffer buildDhcpOfferPacket(final DhcpPacket packet,
final Inet4Address clientAddress, final Integer leaseTimeSec, final short mtu,
final String captivePortalUrl, final Integer ipv6OnlyWaitTime) {
@@ -1434,6 +1432,24 @@
== (byte) ICMPV6_ROUTER_SOLICITATION;
}
+ private boolean isNeighborAdvertisement(final byte[] packetBytes) {
+ ByteBuffer packet = ByteBuffer.wrap(packetBytes);
+ return packet.getShort(ETHER_TYPE_OFFSET) == (short) ETH_P_IPV6
+ && packet.get(ETHER_HEADER_LEN + IPV6_PROTOCOL_OFFSET) == (byte) IPPROTO_ICMPV6
+ && packet.get(ETHER_HEADER_LEN + IPV6_HEADER_LEN)
+ == (byte) ICMPV6_NEIGHBOR_ADVERTISEMENT;
+ }
+
+ private NeighborAdvertisement getNextNeighborAdvertisement() throws ParseException {
+ final byte[] packet = mPacketReader.popPacket(PACKET_TIMEOUT_MS,
+ this::isNeighborAdvertisement);
+ if (packet == null) return null;
+
+ final NeighborAdvertisement na = parseNeighborAdvertisementOrNull(packet);
+ assertNotNull("Invalid neighbour advertisement received", na);
+ return na;
+ }
+
private void waitForRouterSolicitation() throws ParseException {
assertNotNull("No router solicitation received on interface within timeout",
mPacketReader.popPacket(PACKET_TIMEOUT_MS, this::isRouterSolicitation));
@@ -1464,7 +1480,7 @@
private static ByteBuffer buildPioOption(int valid, int preferred, String prefixString)
throws Exception {
return PrefixInformationOption.build(new IpPrefix(prefixString),
- (byte) 0b11000000 /* L = 1, A = 1 */, valid, preferred);
+ (byte) (PIO_FLAG_ON_LINK | PIO_FLAG_AUTONOMOUS), valid, preferred);
}
private static ByteBuffer buildRdnssOption(int lifetime, String... servers) throws Exception {
@@ -2760,4 +2776,50 @@
assertArrayEquals(packet.mUserClass, TEST_OEM_USER_CLASS_INFO);
assertFalse(packet.hasRequestedParam((byte) 42 /* NTP_SERVER */));
}
+
+ private void assertGratuitousNa(final NeighborAdvertisement na) throws Exception {
+ final MacAddress etherMulticast =
+ NetworkStackUtils.ipv6MulticastToEthernetMulticast(IPV6_ADDR_ALL_ROUTERS_MULTICAST);
+ final LinkAddress target = new LinkAddress(na.naHdr.target, 64);
+
+ assertEquals(etherMulticast, na.ethHdr.dstMac);
+ assertEquals(ETH_P_IPV6, na.ethHdr.etherType);
+ assertEquals(IPPROTO_ICMPV6, na.ipv6Hdr.nextHeader);
+ assertEquals(0xff, na.ipv6Hdr.hopLimit);
+ assertTrue(na.ipv6Hdr.srcIp.isLinkLocalAddress());
+ assertEquals(IPV6_ADDR_ALL_ROUTERS_MULTICAST, na.ipv6Hdr.dstIp);
+ assertEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, na.icmpv6Hdr.type);
+ assertEquals(0, na.icmpv6Hdr.code);
+ assertEquals(0, na.naHdr.flags);
+ assertTrue(target.isGlobalPreferred());
+ }
+
+ @Test
+ public void testGratuitousNa() throws Exception {
+ final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+ .withoutIpReachabilityMonitor()
+ .withoutIPv4()
+ .build();
+
+ setFeatureEnabled(NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION,
+ true /* isGratuitousNaEnabled */);
+ assertTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION, false));
+ startIpClientProvisioning(config);
+
+ final InOrder inOrder = inOrder(mCb);
+ final String dnsServer = "2001:4860:4860::64";
+ final ByteBuffer pio = buildPioOption(3600, 1800, "2001:db8:1::/64");
+ final ByteBuffer rdnss = buildRdnssOption(3600, dnsServer);
+ final ByteBuffer ra = buildRaPacket(pio, rdnss);
+
+ doIpv6OnlyProvisioning(inOrder, ra);
+
+ final List<NeighborAdvertisement> naList = new ArrayList<>();
+ NeighborAdvertisement packet;
+ while ((packet = getNextNeighborAdvertisement()) != null) {
+ assertGratuitousNa(packet);
+ naList.add(packet);
+ }
+ assertEquals(2, naList.size()); // privacy address and stable privacy address
+ }
}
diff --git a/tests/integration/src/android/net/ip/IpClientRootTest.kt b/tests/integration/src/android/net/ip/IpClientRootTest.kt
index ea2ec11..68d8aab 100644
--- a/tests/integration/src/android/net/ip/IpClientRootTest.kt
+++ b/tests/integration/src/android/net/ip/IpClientRootTest.kt
@@ -26,12 +26,12 @@
import android.net.ipmemorystore.NetworkAttributes
import android.net.ipmemorystore.Status
import android.net.networkstack.TestNetworkStackServiceClient
-import android.net.util.NetworkStackUtils
import android.os.Process
import android.provider.DeviceConfig
import android.util.ArrayMap
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.net.module.util.DeviceConfigUtils
import java.lang.System.currentTimeMillis
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CountDownLatch
@@ -194,37 +194,33 @@
return ipClientCaptor.value
}
- override fun setDhcpFeatures(
- isDhcpLeaseCacheEnabled: Boolean,
- isRapidCommitEnabled: Boolean,
- isDhcpIpConflictDetectEnabled: Boolean,
- isIPv6OnlyPreferredEnabled: Boolean
- ) {
+ override fun setFeatureEnabled(feature: String, enabled: Boolean) {
automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG)
try {
- setFeatureEnabled(NetworkStackUtils.DHCP_INIT_REBOOT_VERSION, isDhcpLeaseCacheEnabled)
- setFeatureEnabled(NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION, isRapidCommitEnabled)
- setFeatureEnabled(NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION,
- isDhcpIpConflictDetectEnabled)
- setFeatureEnabled(NetworkStackUtils.DHCP_IPV6_ONLY_PREFERRED_VERSION,
- isIPv6OnlyPreferredEnabled)
+ // Do not use computeIfAbsent as it would overwrite null values (flag originally unset)
+ if (!originalFlagValues.containsKey(feature)) {
+ originalFlagValues[feature] =
+ DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature)
+ }
+ // The feature is enabled if the flag is lower than the package version.
+ // Package versions follow a standard format with 9 digits.
+ // TODO: consider resetting flag values on reboot when set to special values like "1" or
+ // "999999999"
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature,
+ if (enabled) "1" else "999999999", false)
} finally {
automation.dropShellPermissionIdentity()
}
}
- private fun setFeatureEnabled(feature: String, enabled: Boolean) {
- // Do not use computeIfAbsent as it would overwrite null values (flag originally unset)
- if (!originalFlagValues.containsKey(feature)) {
- originalFlagValues[feature] =
- DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature)
+ override fun isFeatureEnabled(name: String, defaultEnabled: Boolean): Boolean {
+ automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG)
+ try {
+ return DeviceConfigUtils.isFeatureEnabled(mContext, DeviceConfig.NAMESPACE_CONNECTIVITY,
+ name, defaultEnabled)
+ } finally {
+ automation.dropShellPermissionIdentity()
}
- // The feature is enabled if the flag is lower than the package version.
- // Package versions follow a standard format with 9 digits.
- // TODO: consider resetting flag values on reboot when set to special values like "1" or
- // "999999999"
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature,
- if (enabled) "1" else "999999999", false)
}
private class TestAttributesRetrievedListener : OnNetworkAttributesRetrievedListener {