Merge "Add SDK check to check NOT_VCN_MANAGED"
diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java
index face72c..89c70a9 100644
--- a/src/android/net/ip/IpReachabilityMonitor.java
+++ b/src/android/net/ip/IpReachabilityMonitor.java
@@ -20,6 +20,8 @@
import static android.net.metrics.IpReachabilityEvent.NUD_FAILED_ORGANIC;
import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST;
import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC;
+import static android.net.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import android.content.Context;
import android.net.ConnectivityManager;
@@ -43,8 +45,12 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.net.module.util.DeviceConfigUtils;
import com.android.networkstack.R;
import java.io.PrintWriter;
@@ -143,6 +149,8 @@
protected static final int MIN_NUD_SOLICIT_NUM = 5;
protected static final int MAX_NUD_SOLICIT_INTERVAL_MS = 1000;
protected static final int MIN_NUD_SOLICIT_INTERVAL_MS = 750;
+ protected static final int NUD_MCAST_RESOLICIT_NUM = 3;
+ private static final int INVALID_NUD_MCAST_RESOLICIT_NUM = -1;
public interface Callback {
/**
@@ -161,6 +169,7 @@
interface Dependencies {
void acquireWakeLock(long durationMs);
IpNeighborMonitor makeIpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb);
+ boolean isFeatureEnabled(Context context, String name, boolean defaultEnabled);
static Dependencies makeDefault(Context context, String iface) {
final String lockName = TAG + "." + iface;
@@ -176,6 +185,12 @@
NeighborEventConsumer cb) {
return new IpNeighborMonitor(h, log, cb);
}
+
+ public boolean isFeatureEnabled(final Context context, final String name,
+ boolean defaultEnabled) {
+ return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name,
+ defaultEnabled);
+ }
};
}
}
@@ -183,7 +198,6 @@
private final InterfaceParams mInterfaceParams;
private final IpNeighborMonitor mIpNeighborMonitor;
private final SharedLog mLog;
- private final Callback mCallback;
private final Dependencies mDependencies;
private final boolean mUsingMultinetworkPolicyTracker;
private final ConnectivityManager mCm;
@@ -196,6 +210,8 @@
private volatile long mLastProbeTimeMs;
private int mNumSolicits;
private int mInterSolicitIntervalMs;
+ @NonNull
+ private final Callback mCallback;
public IpReachabilityMonitor(
Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
@@ -225,7 +241,10 @@
// In case the overylaid parameters specify an invalid configuration, set the parameters
// to the hardcoded defaults first, then set them to the values used in the steady state.
try {
- setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS);
+ int numResolicits = isMulticastResolicitEnabled()
+ ? NUD_MCAST_RESOLICIT_NUM
+ : INVALID_NUD_MCAST_RESOLICIT_NUM;
+ setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS, numResolicits);
} catch (Exception e) {
Log.e(TAG, "Failed to adjust neighbor parameters with hardcoded defaults");
}
@@ -241,10 +260,12 @@
// TODO: Consider what to do with other states that are not within
// NeighborEvent#isValid() (i.e. NUD_NONE, NUD_INCOMPLETE).
if (event.nudState == StructNdMsg.NUD_FAILED) {
+ // After both unicast probe and multicast probe(if mcast_resolicit is not 0)
+ // attempts fail, trigger the neighbor lost event and disconnect.
mLog.w("ALERT neighbor went from: " + prev + " to: " + event);
handleNeighborLost(event);
} else if (event.nudState == StructNdMsg.NUD_REACHABLE) {
- maybeRestoreNeighborParameters();
+ handleNeighborReachable(prev, event);
}
});
mIpNeighborMonitor.start();
@@ -296,6 +317,26 @@
return false;
}
+ private boolean hasDefaultRouterNeighborMacAddressChanged(
+ @Nullable final NeighborEvent prev, @NonNull final NeighborEvent event) {
+ if (prev == null || !isNeighborDefaultRouter(event)) return false;
+ return !event.macAddr.equals(prev.macAddr);
+ }
+
+ private boolean isNeighborDefaultRouter(@NonNull final NeighborEvent event) {
+ // For the IPv6 link-local scoped address, equals() works because the NeighborEvent.ip
+ // doesn't have a scope id and Inet6Address#equals doesn't consider scope id neither.
+ for (RouteInfo route : mLinkProperties.getRoutes()) {
+ if (route.isDefaultRoute() && event.ip.equals(route.getGateway())) return true;
+ }
+ return false;
+ }
+
+ private boolean isMulticastResolicitEnabled() {
+ return mDependencies.isFeatureEnabled(mContext, IP_REACHABILITY_MCAST_RESOLICIT_VERSION,
+ false /* defaultEnabled */);
+ }
+
public void updateLinkProperties(LinkProperties lp) {
if (!mInterfaceParams.name.equals(lp.getInterfaceName())) {
// TODO: figure out whether / how to cope with interface changes.
@@ -333,6 +374,24 @@
if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
}
+ private void handleNeighborReachable(@Nullable final NeighborEvent prev,
+ @NonNull final NeighborEvent event) {
+ if (isMulticastResolicitEnabled()
+ && hasDefaultRouterNeighborMacAddressChanged(prev, event)) {
+ // This implies device has confirmed the neighbor's reachability from
+ // other states(e.g., NUD_PROBE or NUD_STALE), checking if the mac
+ // address hasn't changed is required. If Mac address does change, then
+ // trigger a new neighbor lost event and disconnect.
+ final String logMsg = "ALERT neighbor: " + event.ip
+ + " MAC address changed from: " + prev.macAddr
+ + " to: " + event.macAddr;
+ mLog.w(logMsg);
+ mCallback.notifyLost(event.ip, logMsg);
+ return;
+ }
+ maybeRestoreNeighborParameters();
+ }
+
private void handleNeighborLost(NeighborEvent event) {
final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
@@ -370,11 +429,9 @@
if (lostProvisioning) {
final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
Log.w(TAG, logMsg);
- if (mCallback != null) {
- // TODO: remove |ip| when the callback signature no longer has
- // an InetAddress argument.
- mCallback.notifyLost(ip, logMsg);
- }
+ // TODO: remove |ip| when the callback signature no longer has
+ // an InetAddress argument.
+ mCallback.notifyLost(ip, logMsg);
}
logNudFailed(lostProvisioning);
}
@@ -450,6 +507,12 @@
private void setNeighborParameters(int numSolicits, int interSolicitIntervalMs)
throws RemoteException, IllegalArgumentException {
+ // Do not set mcast_resolicit param by default.
+ setNeighborParameters(numSolicits, interSolicitIntervalMs, INVALID_NUD_MCAST_RESOLICIT_NUM);
+ }
+
+ private void setNeighborParameters(int numSolicits, int interSolicitIntervalMs,
+ int numResolicits) throws RemoteException, IllegalArgumentException {
Preconditions.checkArgument(numSolicits >= MIN_NUD_SOLICIT_NUM,
"numSolicits must be at least " + MIN_NUD_SOLICIT_NUM);
Preconditions.checkArgument(numSolicits <= MAX_NUD_SOLICIT_NUM,
@@ -464,6 +527,10 @@
Integer.toString(interSolicitIntervalMs));
mNetd.setProcSysNet(family, INetd.NEIGH, mInterfaceParams.name, "ucast_solicit",
Integer.toString(numSolicits));
+ if (numResolicits != INVALID_NUD_MCAST_RESOLICIT_NUM) {
+ mNetd.setProcSysNet(family, INetd.NEIGH, mInterfaceParams.name, "mcast_resolicit",
+ Integer.toString(numResolicits));
+ }
}
mNumSolicits = numSolicits;
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java
index 81d0c08..e06cdca 100755
--- a/src/android/net/util/NetworkStackUtils.java
+++ b/src/android/net/util/NetworkStackUtils.java
@@ -255,6 +255,13 @@
*/
public static final String IPCLIENT_DISABLE_ACCEPT_RA_VERSION = "ipclient_disable_accept_ra";
+ /**
+ * Experiment flag to enable "mcast_resolicit" neighbor parameter in IpReachabilityMonitor,
+ * set it to 3 by default.
+ */
+ public static final String IP_REACHABILITY_MCAST_RESOLICIT_VERSION =
+ "ip_reachability_mcast_resolicit_version";
+
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 630b05d..2b00fb1 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
@@ -27,6 +27,8 @@
import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
import static android.net.dhcp.DhcpPacket.MIN_V6ONLY_WAIT_MS;
import static android.net.dhcp.DhcpResultsParcelableUtil.fromStableParcelable;
+import static android.net.ip.IpReachabilityMonitor.MIN_NUD_SOLICIT_NUM;
+import static android.net.ip.IpReachabilityMonitor.NUD_MCAST_RESOLICIT_NUM;
import static android.net.ipmemorystore.Status.SUCCESS;
import static android.system.OsConstants.ETH_P_IPV6;
import static android.system.OsConstants.IFA_F_TEMPORARY;
@@ -49,6 +51,7 @@
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.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER;
import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS;
@@ -474,6 +477,11 @@
NeighborEventConsumer cb) {
return new IpNeighborMonitor(h, log, cb);
}
+
+ public boolean isFeatureEnabled(final Context context, final String name,
+ boolean defaultEnabled) {
+ return Dependencies.this.isFeatureEnabled(context, name, defaultEnabled);
+ }
};
}
@@ -3094,7 +3102,50 @@
assertNeighborSolicitation(ns, target);
}
+ private void assertMulticastNeighborSolicitation(final NeighborSolicitation ns,
+ final Inet6Address target) {
+ final MacAddress etherMulticast =
+ NetworkStackUtils.ipv6MulticastToEthernetMulticast(ns.ipv6Hdr.dstIp);
+ assertEquals(etherMulticast, ns.ethHdr.dstMac);
+ assertTrue(ns.ipv6Hdr.dstIp.isMulticastAddress());
+ assertNeighborSolicitation(ns, target);
+ }
+
+ private NeighborSolicitation waitForUnicastNeighborSolicitation(final MacAddress dstMac,
+ final Inet6Address dstIp, final Inet6Address targetIp) throws Exception {
+ NeighborSolicitation ns;
+ while ((ns = getNextNeighborSolicitation()) != null) {
+ // Filter out the NSes used for duplicate address detetction, the target address
+ // is the global IPv6 address inside these NSes.
+ if (ns.nsHdr.target.isLinkLocalAddress()) break;
+ }
+ assertNotNull("No unicast Neighbor solicitation received on interface within timeout", ns);
+ assertUnicastNeighborSolicitation(ns, dstMac, dstIp, targetIp);
+ return ns;
+ }
+
+ private List<NeighborSolicitation> waitForMultipleNeighborSolicitations() throws Exception {
+ NeighborSolicitation ns;
+ final List<NeighborSolicitation> nsList = new ArrayList<NeighborSolicitation>();
+ while ((ns = getNextNeighborSolicitation()) != null) {
+ // Filter out the NSes used for duplicate address detetction, the target address
+ // is the global IPv6 address inside these NSes.
+ if (ns.nsHdr.target.isLinkLocalAddress()) {
+ nsList.add(ns);
+ }
+ }
+ assertFalse(nsList.isEmpty());
+ return nsList;
+ }
+
+ // Override this function with disabled experiment flag by default, in order not to
+ // affect those tests which are just related to basic IpReachabilityMonitor infra.
private void prepareIpReachabilityMonitorTest() throws Exception {
+ prepareIpReachabilityMonitorTest(false /* isMulticastResolicitEnabled */);
+ }
+
+ private void prepareIpReachabilityMonitorTest(boolean isMulticastResolicitEnabled)
+ throws Exception {
final ScanResultInfo info = makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID);
ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
.withLayer2Information(new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
@@ -3103,6 +3154,8 @@
.withDisplayName(TEST_DEFAULT_SSID)
.withoutIPv4()
.build();
+ setFeatureEnabled(NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION,
+ isMulticastResolicitEnabled);
startIpClientProvisioning(config);
verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(false);
doIpv6OnlyProvisioning();
@@ -3115,16 +3168,8 @@
public void testIpReachabilityMonitor_probeFailed() throws Exception {
prepareIpReachabilityMonitorTest();
- NeighborSolicitation packet;
- final List<NeighborSolicitation> nsList = new ArrayList<NeighborSolicitation>();
- while ((packet = getNextNeighborSolicitation()) != null) {
- // Filter out the NSes used for duplicate address detetction, the target address
- // is the global IPv6 address inside these NSes.
- if (packet.nsHdr.target.isLinkLocalAddress()) {
- nsList.add(packet);
- }
- }
- assertEquals(IpReachabilityMonitor.MIN_NUD_SOLICIT_NUM, nsList.size());
+ final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations();
+ assertEquals(MIN_NUD_SOLICIT_NUM, nsList.size());
for (NeighborSolicitation ns : nsList) {
assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
@@ -3136,13 +3181,7 @@
public void testIpReachabilityMonitor_probeReachable() throws Exception {
prepareIpReachabilityMonitorTest();
- NeighborSolicitation ns;
- while ((ns = getNextNeighborSolicitation()) != null) {
- // Filter out the NSes used for duplicate address detetction, the target address
- // is the global IPv6 address inside these NSes.
- if (ns.nsHdr.target.isLinkLocalAddress()) break;
- }
- assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
+ final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */,
ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
// Reply Neighbor Advertisement and check notifyLost callback won't be triggered.
@@ -3153,4 +3192,61 @@
mPacketReader.sendResponse(na);
assertNeverNotifyNeighborLost();
}
+
+ @Test
+ public void testIpReachabilityMonitor_mcastResoclicitProbeFailed() throws Exception {
+ prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
+
+ final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations();
+ int expectedSize = MIN_NUD_SOLICIT_NUM + NUD_MCAST_RESOLICIT_NUM;
+ assertEquals(expectedSize, nsList.size()); // 5 unicast NSes + 3 multicast NSes
+ for (NeighborSolicitation ns : nsList.subList(0, MIN_NUD_SOLICIT_NUM)) {
+ assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
+ ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
+ }
+ for (NeighborSolicitation ns : nsList.subList(MIN_NUD_SOLICIT_NUM, nsList.size())) {
+ assertMulticastNeighborSolicitation(ns, ROUTER_LINK_LOCAL /* targetIp */);
+ }
+ assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */);
+ }
+
+ @Test
+ public void testIpReachabilityMonitor_mcastResoclicitProbeReachableWithSameLinkLayerAddress()
+ throws Exception {
+ prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
+
+ final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */,
+ ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
+
+ // Reply Neighbor Advertisement and check notifyLost callback won't be triggered.
+ int flag = NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER | NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
+ final ByteBuffer na = NeighborAdvertisement.build(ROUTER_MAC /* srcMac */,
+ ns.ethHdr.srcMac /* dstMac */, ROUTER_LINK_LOCAL /* srcIp */,
+ ns.ipv6Hdr.srcIp /* dstIp */, flag, ROUTER_LINK_LOCAL /* target */);
+ mPacketReader.sendResponse(na);
+ assertNeverNotifyNeighborLost();
+ }
+
+ @Test
+ public void testIpReachabilityMonitor_mcastResoclicitProbeReachableWithDiffLinkLayerAddress()
+ throws Exception {
+ prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
+
+ final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */,
+ ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
+
+ // Reply Neighbor Advertisement with a different link-layer address and check notifyLost
+ // callback will be triggered. Override flag must be set, which indicates that the
+ // advertisement should override an existing cache entry and update the cached link-layer
+ // address, otherwise, kernel won't transit to REACHABLE state with a different link-layer
+ // address.
+ int flag = NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER | NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED
+ | NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
+ final MacAddress newMac = MacAddress.fromString("00:1a:11:22:33:55");
+ final ByteBuffer na = NeighborAdvertisement.build(newMac /* srcMac */,
+ ns.ethHdr.srcMac /* dstMac */, ROUTER_LINK_LOCAL /* srcIp */,
+ ns.ipv6Hdr.srcIp /* dstIp */, flag, ROUTER_LINK_LOCAL /* target */);
+ mPacketReader.sendResponse(na);
+ assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */);
+ }
}
diff --git a/tests/unit/lint-baseline.xml b/tests/unit/lint-baseline.xml
index 0812c2d..0bfcaa9 100644
--- a/tests/unit/lint-baseline.xml
+++ b/tests/unit/lint-baseline.xml
@@ -78,81 +78,4 @@
column="14"/>
</issue>
- <issue
- id="NewApi"
- message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
- errorLine1=" mCallback.onCapabilitiesChanged(net2097, NetworkCapabilities())"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
- line="59"
- column="50"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
- errorLine1=" mCallback.onCapabilitiesChanged(net2098, NetworkCapabilities())"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
- line="71"
- column="50"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
- errorLine1=" val meteredNc = NetworkCapabilities()"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
- line="108"
- column="25"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
- errorLine1=" val unmeteredNc = NetworkCapabilities().addCapability(NOT_METERED)"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
- line="109"
- column="27"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
- errorLine1=" val netCaps = NetworkCapabilities().addTransportType(CELLULAR)"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
- line="130"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
- errorLine1=" val netCaps = NetworkCapabilities().addCapability(NOT_METERED).addTransportType(WIFI)"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
- line="152"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
- errorLine1=" "CapabilitiesChanged" -> cb.onCapabilitiesChanged(net, NetworkCapabilities())"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
- line="279"
- column="68"/>
- </issue>
-
</issues>
diff --git a/tests/unit/src/android/net/netlink/NetlinkSocketTest.java b/tests/unit/src/android/net/netlink/NetlinkSocketTest.java
index 5716803..6a84a85 100644
--- a/tests/unit/src/android/net/netlink/NetlinkSocketTest.java
+++ b/tests/unit/src/android/net/netlink/NetlinkSocketTest.java
@@ -17,21 +17,36 @@
package android.net.netlink;
import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.AF_UNSPEC;
+import static android.system.OsConstants.EACCES;
import static android.system.OsConstants.NETLINK_ROUTE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import android.content.Context;
import android.net.netlink.NetlinkSocket;
import android.net.netlink.RtNetlinkNeighborMessage;
import android.net.netlink.StructNlMsgHdr;
+import android.system.ErrnoException;
import android.system.NetlinkSocketAddress;
import android.system.Os;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
import libcore.io.IoUtils;
import org.junit.Test;
@@ -47,7 +62,7 @@
private final String TAG = "NetlinkSocketTest";
@Test
- public void testBasicWorkingGetNeighborsQuery() throws Exception {
+ public void testGetNeighborsQuery() throws Exception {
final FileDescriptor fd = NetlinkSocket.forProto(NETLINK_ROUTE);
assertNotNull(fd);
@@ -63,6 +78,25 @@
assertNotNull(req);
final long TIMEOUT = 500;
+ final Context ctx = InstrumentationRegistry.getInstrumentation().getContext();
+ final int targetSdk =
+ ctx.getPackageManager()
+ .getApplicationInfo(ctx.getPackageName(), 0)
+ .targetSdkVersion;
+
+ // Apps targeting an SDK version > S are not allowed to send RTM_GETNEIGH{TBL} messages
+ if (SdkLevel.isAtLeastT() && targetSdk > 31) {
+ try {
+ NetlinkSocket.sendMessage(fd, req, 0, req.length, TIMEOUT);
+ fail("RTM_GETNEIGH is not allowed for apps targeting SDK > 31 on T+ platforms");
+ } catch (ErrnoException e) {
+ // Expected
+ assertEquals(e.errno, EACCES);
+ return;
+ }
+ }
+
+ // Check that apps targeting lower API levels / running on older platforms succeed
assertEquals(req.length, NetlinkSocket.sendMessage(fd, req, 0, req.length, TIMEOUT));
int neighMessageCount = 0;
@@ -103,4 +137,105 @@
IoUtils.closeQuietly(fd);
}
+
+ @Test
+ public void testBasicWorkingGetAddrQuery() throws Exception {
+ final FileDescriptor fd = NetlinkSocket.forProto(NETLINK_ROUTE);
+ assertNotNull(fd);
+
+ NetlinkSocket.connectToKernel(fd);
+
+ final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd);
+ assertNotNull(localAddr);
+ assertEquals(0, localAddr.getGroupsMask());
+ assertTrue(0 != localAddr.getPortId());
+
+ final int testSeqno = 8;
+ final byte[] req = newGetAddrRequest(testSeqno);
+ assertNotNull(req);
+
+ final long timeout = 500;
+ assertEquals(req.length, NetlinkSocket.sendMessage(fd, req, 0, req.length, timeout));
+
+ int addrMessageCount = 0;
+
+ while (true) {
+ ByteBuffer response = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, timeout);
+ assertNotNull(response);
+ assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit());
+ assertEquals(0, response.position());
+ assertEquals(ByteOrder.nativeOrder(), response.order());
+
+ final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(response);
+ assertNotNull(nlmsghdr);
+
+ if (nlmsghdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) {
+ break;
+ }
+
+ assertEquals(NetlinkConstants.RTM_NEWADDR, nlmsghdr.nlmsg_type);
+ assertTrue((nlmsghdr.nlmsg_flags & StructNlMsgHdr.NLM_F_MULTI) != 0);
+ assertEquals(testSeqno, nlmsghdr.nlmsg_seq);
+ assertEquals(localAddr.getPortId(), nlmsghdr.nlmsg_pid);
+ addrMessageCount++;
+
+ final IfaddrMsg ifaMsg = Struct.parse(IfaddrMsg.class, response);
+ assertTrue(
+ "Non-IP address family: " + ifaMsg.family,
+ ifaMsg.family == AF_INET || ifaMsg.family == AF_INET6);
+ }
+
+ assertTrue(addrMessageCount > 0);
+
+ IoUtils.closeQuietly(fd);
+ }
+
+ /** A convenience method to create an RTM_GETADDR request message. */
+ private static byte[] newGetAddrRequest(int seqNo) {
+ final int length = StructNlMsgHdr.STRUCT_SIZE + Struct.getSize(RtgenMsg.class);
+ final byte[] bytes = new byte[length];
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+ byteBuffer.order(ByteOrder.nativeOrder());
+
+ final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
+ nlmsghdr.nlmsg_len = length;
+ nlmsghdr.nlmsg_type = NetlinkConstants.RTM_GETADDR;
+ nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ nlmsghdr.nlmsg_seq = seqNo;
+ nlmsghdr.pack(byteBuffer);
+
+ final RtgenMsg rtgenMsg = new RtgenMsg();
+ rtgenMsg.family = (byte) AF_UNSPEC;
+ rtgenMsg.writeToByteBuffer(byteBuffer);
+
+ return bytes;
+ }
+
+ /** From uapi/linux/rtnetlink.h */
+ private static class RtgenMsg extends Struct {
+ @Field(order = 0, type = Type.U8)
+ public short family;
+ }
+
+ /**
+ * From uapi/linux/ifaddr.h
+ *
+ * Public ensures visibility to Struct class
+ */
+ public static class IfaddrMsg extends Struct {
+ @Field(order = 0, type = Type.U8)
+ public short family;
+
+ @Field(order = 1, type = Type.U8)
+ public short prefixlen;
+
+ @Field(order = 2, type = Type.U8)
+ public short flags;
+
+ @Field(order = 3, type = Type.U8)
+ public short scope;
+
+ @Field(order = 4, type = Type.U32)
+ public long index;
+ }
}
diff --git a/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt b/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt
index 5b91985..6f495e7 100644
--- a/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt
+++ b/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt
@@ -1,5 +1,6 @@
package android.net.testutils
+import android.annotation.SuppressLint
import android.net.LinkAddress
import android.net.LinkProperties
import android.net.Network
@@ -7,6 +8,15 @@
import com.android.testutils.ConcurrentInterpreter
import com.android.testutils.InterpretMatcher
import com.android.testutils.RecorderCallback.CallbackEntry
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.AVAILABLE
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.BLOCKED_STATUS
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LINK_PROPERTIES_CHANGED
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LOSING
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.NETWORK_CAPS_UPDATED
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LOST
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.RESUMED
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.SUSPENDED
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.UNAVAILABLE
import com.android.testutils.RecorderCallback.CallbackEntry.Available
import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
@@ -33,6 +43,7 @@
const val TEST_INTERFACE_NAME = "testInterfaceName"
@RunWith(JUnit4::class)
+@SuppressLint("NewApi") // Uses hidden APIs, which the linter would identify as missing APIs.
class TestableNetworkCallbackTest {
private lateinit var mCallback: TestableNetworkCallback
@@ -245,6 +256,41 @@
onBlockedStatus(199) | poll(1) = BlockedStatus(199) time 0..3
""")
}
+
+ @Test
+ fun testEventuallyExpect() {
+ // TODO: Current test does not verify the inline one. Also verify the behavior after
+ // aligning two eventuallyExpect()
+ val net1 = Network(100)
+ val net2 = Network(101)
+ mCallback.onAvailable(net1)
+ mCallback.onCapabilitiesChanged(net1, NetworkCapabilities())
+ mCallback.onLinkPropertiesChanged(net1, LinkProperties())
+ mCallback.eventuallyExpect(LINK_PROPERTIES_CHANGED) {
+ net1.equals(it.network)
+ }
+ // No further new callback. Expect no callback.
+ assertFails { mCallback.eventuallyExpect(LINK_PROPERTIES_CHANGED) }
+
+ // Verify no predicate set.
+ mCallback.onAvailable(net2)
+ mCallback.onLinkPropertiesChanged(net2, LinkProperties())
+ mCallback.onBlockedStatusChanged(net1, false)
+ mCallback.eventuallyExpect(BLOCKED_STATUS) { net1.equals(it.network) }
+ // Verify no callback received if the callback does not happen.
+ assertFails { mCallback.eventuallyExpect(LOSING) }
+ }
+
+ @Test
+ fun testEventuallyExpectOnMultiThreads() {
+ TNCInterpreter.interpretTestSpec(initial = mCallback, lineShift = 1,
+ threadTransform = { cb -> cb.createLinkedCopy() }, spec = """
+ onAvailable(100) | eventually(CapabilitiesChanged(100), 1) fails
+ sleep ; onCapabilitiesChanged(100) | eventually(CapabilitiesChanged(100), 2)
+ onAvailable(101) ; onBlockedStatus(101) | eventually(BlockedStatus(100), 2) fails
+ onSuspended(100) ; sleep ; onLost(100) | eventually(Lost(100), 2)
+ """)
+ }
}
private object TNCInterpreter : ConcurrentInterpreter<TestableNetworkCallback>(interpretTable)
@@ -254,6 +300,7 @@
return CallbackEntry::class.sealedSubclasses.first { it.simpleName == name }
}
+@SuppressLint("NewApi") // Uses hidden APIs, which the linter would identify as missing APIs.
private val interpretTable = listOf<InterpretMatcher<TestableNetworkCallback>>(
// Interpret "Available(xx)" as "call to onAvailable with netId xx", and likewise for
// all callback types. This is implemented above by enumerating the subclasses of
@@ -289,5 +336,26 @@
},
Regex("""poll\((\d+)\)""") to { i, cb, t ->
cb.pollForNextCallback(t.timeArg(1))
+ },
+ // Interpret "eventually(Available(xx), timeout)" as calling eventuallyExpect that expects
+ // CallbackEntry.AVAILABLE with netId of xx within timeout*INTERPRET_TIME_UNIT timeout, and
+ // likewise for all callback types.
+ Regex("""eventually\(($EntryList)\((\d+)\),\s+(\d+)\)""") to { i, cb, t ->
+ val net = Network(t.intArg(2))
+ val timeout = t.timeArg(3)
+ when (t.strArg(1)) {
+ "Available" -> cb.eventuallyExpect(AVAILABLE, timeout) { net == it.network }
+ "Suspended" -> cb.eventuallyExpect(SUSPENDED, timeout) { net == it.network }
+ "Resumed" -> cb.eventuallyExpect(RESUMED, timeout) { net == it.network }
+ "Losing" -> cb.eventuallyExpect(LOSING, timeout) { net == it.network }
+ "Lost" -> cb.eventuallyExpect(LOST, timeout) { net == it.network }
+ "Unavailable" -> cb.eventuallyExpect(UNAVAILABLE, timeout) { net == it.network }
+ "BlockedStatus" -> cb.eventuallyExpect(BLOCKED_STATUS, timeout) { net == it.network }
+ "CapabilitiesChanged" ->
+ cb.eventuallyExpect(NETWORK_CAPS_UPDATED, timeout) { net == it.network }
+ "LinkPropertiesChanged" ->
+ cb.eventuallyExpect(LINK_PROPERTIES_CHANGED, timeout) { net == it.network }
+ else -> fail("Unknown callback type")
+ }
}
)