Loading src/android/net/ip/IpClient.java +81 −25 Original line number Diff line number Diff line Loading @@ -18,13 +18,18 @@ package android.net.ip; import static android.net.RouteInfo.RTN_UNICAST; import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable; 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; import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static android.system.OsConstants.AF_PACKET; import static android.system.OsConstants.ETH_P_ARP; 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.ARP_REPLY; import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST; 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; Loading Loading @@ -91,6 +96,7 @@ 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.arp.ArpPacket; import com.android.networkstack.metrics.IpProvisioningMetrics; import com.android.networkstack.packets.NeighborAdvertisement; import com.android.server.NetworkObserverRegistry; Loading @@ -98,6 +104,7 @@ import com.android.server.NetworkStackService.NetworkStackServiceManager; import java.io.FileDescriptor; import java.io.PrintWriter; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.MalformedURLException; Loading Loading @@ -839,6 +846,11 @@ public class IpClient extends StateMachine { false /* defaultEnabled */); } private boolean isGratuitousArpNaRoamingEnabled() { return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GARP_NA_ROAMING_VERSION, false /* defaultEnabled */); } @Override protected void onQuitting() { mCallback.onQuit(); Loading Loading @@ -1423,30 +1435,47 @@ public class IpClient extends StateMachine { } } 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); private void transmitPacket(final ByteBuffer packet, final SocketAddress sockAddress, final String msg) { 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); logError(msg, e); } finally { NetworkStackUtils.closeSocketQuietly(sock); } } private Inet6Address getIpv6LinkLocalAddress(final LinkProperties newLp) { Inet6Address src = null; private void sendGratuitousNA(final Inet6Address srcIp, final Inet6Address targetIp) { 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, targetIp); final SocketAddress sockAddress = SocketUtilsShimImpl.newInstance().makePacketSocketAddress(ETH_P_IPV6, mInterfaceParams.index, dstMac.toByteArray()); transmitPacket(packet, sockAddress, "Failed to send Gratuitous Neighbor Advertisement"); } private void sendGratuitousARP(final Inet4Address srcIp) { final ByteBuffer packet = ArpPacket.buildArpPacket(ETHER_BROADCAST /* dstMac */, mInterfaceParams.macAddr.toByteArray() /* srcMac */, srcIp.getAddress() /* targetIp */, ETHER_BROADCAST /* targetHwAddress */, srcIp.getAddress() /* senderIp */, (short) ARP_REPLY); final SocketAddress sockAddress = makePacketSocketAddress(ETH_P_ARP, mInterfaceParams.index); transmitPacket(packet, sockAddress, "Failed to send GARP"); } private static Inet6Address getIpv6LinkLocalAddress(final LinkProperties newLp) { for (LinkAddress la : newLp.getLinkAddresses()) { if (!la.isIpv6()) continue; final Inet6Address ip = (Inet6Address) la.getAddress(); Loading @@ -1455,22 +1484,40 @@ public class IpClient extends StateMachine { return null; } private void maybeSendGratuitousNAs(final LinkProperties newLp) { if (!newLp.hasGlobalIpv6Address() || !isGratuitousNaEnabled()) return; private void maybeSendGratuitousNAs(final LinkProperties lp, boolean afterRoaming) { if (!lp.hasGlobalIpv6Address()) return; final Inet6Address src = getIpv6LinkLocalAddress(newLp); if (src == null) return; for (LinkAddress la : newLp.getLinkAddresses()) { final Inet6Address srcIp = getIpv6LinkLocalAddress(lp); if (srcIp == null) return; // TODO: add experiment with sending only one gratuitous NA packet instead of one // packet per address. for (LinkAddress la : lp.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; // Already sent gratuitous NA with this target global IPv6 address. But for // the L2 roaming case, device should always (re)transmit Gratuitous NA for // each IPv6 global unicast address respectively after roaming. if (!afterRoaming && mGratuitousNaTargetAddresses.contains(targetIp)) continue; if (DBG) { Log.d(mTag, "send Gratuitous NA from " + src + ", target Address is " + targetIp); mLog.log("send Gratuitous NA from " + srcIp.getHostAddress() + " for " + targetIp.getHostAddress() + (afterRoaming ? " after roaming" : "")); } sendGratuitousNA(srcIp, targetIp); if (!afterRoaming) mGratuitousNaTargetAddresses.add(targetIp); } } private void maybeSendGratuitousARP(final LinkProperties lp) { for (LinkAddress address : lp.getLinkAddresses()) { if (address.getAddress() instanceof Inet4Address) { final Inet4Address srcIp = (Inet4Address) address.getAddress(); if (DBG) { mLog.log("send GARP for " + srcIp.getHostAddress() + " HW address: " + mInterfaceParams.macAddr); } sendGratuitousARP(srcIp); } sendGratuitousNA(src, targetIp); mGratuitousNaTargetAddresses.add(targetIp); } } Loading @@ -1484,7 +1531,9 @@ public class IpClient extends StateMachine { // 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); if (isGratuitousNaEnabled()) { maybeSendGratuitousNAs(newLp, false /* isGratuitousNaAfterRoaming */); } // Either success IPv4 or IPv6 provisioning triggers new LinkProperties update, // wait for the provisioning completion and record the latency. Loading Loading @@ -1741,6 +1790,13 @@ public class IpClient extends StateMachine { // If the BSSID has not changed, there is nothing to do. if (info.bssid.equals(mCurrentBssid)) return; // Before trigger probing to the interesting neighbors, send Gratuitous ARP // and Neighbor Advertisment in advance to propgate host's IPv4/v6 addresses. if (isGratuitousArpNaRoamingEnabled()) { maybeSendGratuitousARP(mLinkProperties); maybeSendGratuitousNAs(mLinkProperties, true /* isGratuitousNaAfterRoaming */); } if (mIpReachabilityMonitor != null) { mIpReachabilityMonitor.probeAll(); } Loading src/android/net/util/NetworkStackUtils.java +7 −0 Original line number Diff line number Diff line Loading @@ -242,6 +242,13 @@ public class NetworkStackUtils { */ public static final String IPCLIENT_GRATUITOUS_NA_VERSION = "ipclient_gratuitous_na_version"; /** * Experiment flag to enable sending Gratuitous APR and Gratuitous Neighbor Advertisement for * all assigned IPv4 and IPv6 GUAs after completing L2 roaming. */ public static final String IPCLIENT_GARP_NA_ROAMING_VERSION = "ipclient_garp_na_roaming_version"; static { System.loadLibrary("networkstackutilsjni"); } Loading tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java +146 −25 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4 import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY; import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST; import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN; import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST; 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; Loading Loading @@ -1121,6 +1122,14 @@ public abstract class IpClientIntegrationTestCommon { assertEquals(packet.senderIp, CLIENT_ADDR); } private void assertGratuitousARP(final ArpPacket packet) { assertEquals(packet.opCode, ARP_REPLY); assertEquals(packet.senderIp, CLIENT_ADDR); assertEquals(packet.targetIp, CLIENT_ADDR); assertTrue(Arrays.equals(packet.senderHwAddress.toByteArray(), mClientMac)); assertTrue(Arrays.equals(packet.targetHwAddress.toByteArray(), ETHER_BROADCAST)); } private void doIpAddressConflictDetectionTest(final boolean causeIpAddressConflict, final boolean shouldReplyRapidCommitAck, final boolean isDhcpIpConflictDetectEnabled, final boolean shouldResponseArpReply) throws Exception { Loading Loading @@ -1537,6 +1546,16 @@ public abstract class IpClientIntegrationTestCommon { return addr.isGlobalPreferred() && hasFlag(addr, flag); } private LinkProperties doIpv6OnlyProvisioning() throws Exception { 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); return doIpv6OnlyProvisioning(inOrder, ra); } private LinkProperties doIpv6OnlyProvisioning(InOrder inOrder, ByteBuffer ra) throws Exception { waitForRouterSolicitation(); mPacketReader.sendResponse(ra); Loading Loading @@ -2234,6 +2253,14 @@ public abstract class IpClientIntegrationTestCommon { true /* expectMetered */); } private void forceLayer2Roaming() throws Exception { final Layer2InformationParcelable roamingInfo = new Layer2InformationParcelable(); roamingInfo.bssid = MacAddress.fromString(TEST_DHCP_ROAM_BSSID); roamingInfo.l2Key = TEST_DHCP_ROAM_L2KEY; roamingInfo.cluster = TEST_DHCP_ROAM_CLUSTER; mIIpClient.updateLayer2Information(roamingInfo); } private void doDhcpRoamingTest(final boolean hasMismatchedIpAddress, final String displayName, final String ssid, final String bssid, final boolean expectRoaming) throws Exception { long currentTime = System.currentTimeMillis(); Loading @@ -2260,11 +2287,7 @@ public abstract class IpClientIntegrationTestCommon { assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU); // simulate the roaming by updating bssid. final Layer2InformationParcelable roamingInfo = new Layer2InformationParcelable(); roamingInfo.bssid = MacAddress.fromString(TEST_DHCP_ROAM_BSSID); roamingInfo.l2Key = TEST_DHCP_ROAM_L2KEY; roamingInfo.cluster = TEST_DHCP_ROAM_CLUSTER; mIpc.updateLayer2Information(roamingInfo); forceLayer2Roaming(); currentTime = System.currentTimeMillis(); reset(mIpMemoryStore); Loading Loading @@ -2338,18 +2361,7 @@ public abstract class IpClientIntegrationTestCommon { TEST_DHCP_ROAM_SSID, TEST_DEFAULT_BSSID, true /* expectRoaming */); } private void doDualStackProvisioning() throws Exception { when(mCm.shouldAvoidBadWifi()).thenReturn(true); final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() .build(); // Enable rapid commit to accelerate DHCP handshake to shorten test duration, // not strictly necessary. setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); mIpc.startProvisioning(config); private void performDualStackProvisioning() throws Exception { final InOrder inOrder = inOrder(mCb); final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>(); final String dnsServer = "2001:4860:4860::64"; Loading @@ -2376,6 +2388,21 @@ public abstract class IpClientIntegrationTestCommon { reset(mCb); } private void doDualStackProvisioning() throws Exception { when(mCm.shouldAvoidBadWifi()).thenReturn(true); final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() .build(); // Enable rapid commit to accelerate DHCP handshake to shorten test duration, // not strictly necessary. setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); mIpc.startProvisioning(config); performDualStackProvisioning(); } @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required") public void testIgnoreIpv6ProvisioningLoss() throws Exception { doDualStackProvisioning(); Loading Loading @@ -2796,7 +2823,7 @@ public abstract class IpClientIntegrationTestCommon { } @Test public void testGratuitousNa() throws Exception { public void testGratuitousNaForNewGlobalUnicastAddresses() throws Exception { final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() .withoutIPv4() Loading @@ -2807,13 +2834,7 @@ public abstract class IpClientIntegrationTestCommon { 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); doIpv6OnlyProvisioning(); final List<NeighborAdvertisement> naList = new ArrayList<>(); NeighborAdvertisement packet; Loading @@ -2823,4 +2844,104 @@ public abstract class IpClientIntegrationTestCommon { } assertEquals(2, naList.size()); // privacy address and stable privacy address } private void startGratuitousArpAndNaAfterRoamingTest(boolean isGratuitousArpNaRoamingEnabled, boolean hasIpv4, boolean hasIpv6) throws Exception { final ScanResultInfo scanResultInfo = makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID); final ProvisioningConfiguration.Builder prov = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() .withScanResultInfo(scanResultInfo) .withDisplayName("ssid"); if (!hasIpv4) prov.withoutIPv4(); if (!hasIpv6) prov.withoutIPv6(); // Enable rapid commit to accelerate DHCP handshake to shorten test duration, // not strictly necessary. setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); if (isGratuitousArpNaRoamingEnabled) { setFeatureEnabled(NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION, true); assertTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION, false)); } startIpClientProvisioning(prov.build()); } private void waitForGratuitousArpAndNaPacket(final List<ArpPacket> arpList, final List<NeighborAdvertisement> naList) throws Exception { NeighborAdvertisement na; ArpPacket garp; do { na = getNextNeighborAdvertisement(); if (na != null) { assertGratuitousNa(na); naList.add(na); } garp = getNextArpPacket(TEST_TIMEOUT_MS); if (garp != null) { assertGratuitousARP(garp); arpList.add(garp); } } while (na != null || garp != null); } @Test public void testGratuitousArpAndNaAfterRoaming() throws Exception { startGratuitousArpAndNaAfterRoamingTest(true /* isGratuitousArpNaRoamingEnabled */, true /* hasIpv4 */, true /* hasIpv6 */); performDualStackProvisioning(); forceLayer2Roaming(); final List<ArpPacket> arpList = new ArrayList<>(); final List<NeighborAdvertisement> naList = new ArrayList<>(); waitForGratuitousArpAndNaPacket(arpList, naList); assertEquals(2, naList.size()); // privacy address and stable privacy address assertEquals(1, arpList.size()); // IPv4 address } @Test public void testGratuitousArpAndNaAfterRoaming_disableExpFlag() throws Exception { startGratuitousArpAndNaAfterRoamingTest(false /* isGratuitousArpNaRoamingEnabled */, true /* hasIpv6 */, true /* hasIpv6 */); performDualStackProvisioning(); forceLayer2Roaming(); final List<ArpPacket> arpList = new ArrayList<>(); final List<NeighborAdvertisement> naList = new ArrayList<>(); waitForGratuitousArpAndNaPacket(arpList, naList); assertEquals(0, naList.size()); assertEquals(0, arpList.size()); } @Test public void testGratuitousArpAndNaAfterRoaming_IPv6OnlyNetwork() throws Exception { startGratuitousArpAndNaAfterRoamingTest(true /* isGratuitousArpNaRoamingEnabled */, false /* hasIpv4 */, true /* hasIpv6 */); doIpv6OnlyProvisioning(); forceLayer2Roaming(); final List<ArpPacket> arpList = new ArrayList<>(); final List<NeighborAdvertisement> naList = new ArrayList<>(); waitForGratuitousArpAndNaPacket(arpList, naList); assertEquals(2, naList.size()); assertEquals(0, arpList.size()); } @Test public void testGratuitousArpAndNaAfterRoaming_IPv4OnlyNetwork() throws Exception { startGratuitousArpAndNaAfterRoamingTest(true /* isGratuitousArpNaRoamingEnabled */, true /* hasIpv4 */, false /* hasIpv6 */); // Start IPv4 provisioning and wait until entire provisioning completes. handleDhcpPackets(true /* isSuccessLease */, TEST_LEASE_DURATION_S, true /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, null /* serverSentUrl */); verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR)); forceLayer2Roaming(); final List<ArpPacket> arpList = new ArrayList<>(); final List<NeighborAdvertisement> naList = new ArrayList<>(); waitForGratuitousArpAndNaPacket(arpList, naList); assertEquals(0, naList.size()); assertEquals(1, arpList.size()); } } Loading
src/android/net/ip/IpClient.java +81 −25 Original line number Diff line number Diff line Loading @@ -18,13 +18,18 @@ package android.net.ip; import static android.net.RouteInfo.RTN_UNICAST; import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable; 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; import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static android.system.OsConstants.AF_PACKET; import static android.system.OsConstants.ETH_P_ARP; 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.ARP_REPLY; import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST; 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; Loading Loading @@ -91,6 +96,7 @@ 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.arp.ArpPacket; import com.android.networkstack.metrics.IpProvisioningMetrics; import com.android.networkstack.packets.NeighborAdvertisement; import com.android.server.NetworkObserverRegistry; Loading @@ -98,6 +104,7 @@ import com.android.server.NetworkStackService.NetworkStackServiceManager; import java.io.FileDescriptor; import java.io.PrintWriter; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.MalformedURLException; Loading Loading @@ -839,6 +846,11 @@ public class IpClient extends StateMachine { false /* defaultEnabled */); } private boolean isGratuitousArpNaRoamingEnabled() { return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GARP_NA_ROAMING_VERSION, false /* defaultEnabled */); } @Override protected void onQuitting() { mCallback.onQuit(); Loading Loading @@ -1423,30 +1435,47 @@ public class IpClient extends StateMachine { } } 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); private void transmitPacket(final ByteBuffer packet, final SocketAddress sockAddress, final String msg) { 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); logError(msg, e); } finally { NetworkStackUtils.closeSocketQuietly(sock); } } private Inet6Address getIpv6LinkLocalAddress(final LinkProperties newLp) { Inet6Address src = null; private void sendGratuitousNA(final Inet6Address srcIp, final Inet6Address targetIp) { 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, targetIp); final SocketAddress sockAddress = SocketUtilsShimImpl.newInstance().makePacketSocketAddress(ETH_P_IPV6, mInterfaceParams.index, dstMac.toByteArray()); transmitPacket(packet, sockAddress, "Failed to send Gratuitous Neighbor Advertisement"); } private void sendGratuitousARP(final Inet4Address srcIp) { final ByteBuffer packet = ArpPacket.buildArpPacket(ETHER_BROADCAST /* dstMac */, mInterfaceParams.macAddr.toByteArray() /* srcMac */, srcIp.getAddress() /* targetIp */, ETHER_BROADCAST /* targetHwAddress */, srcIp.getAddress() /* senderIp */, (short) ARP_REPLY); final SocketAddress sockAddress = makePacketSocketAddress(ETH_P_ARP, mInterfaceParams.index); transmitPacket(packet, sockAddress, "Failed to send GARP"); } private static Inet6Address getIpv6LinkLocalAddress(final LinkProperties newLp) { for (LinkAddress la : newLp.getLinkAddresses()) { if (!la.isIpv6()) continue; final Inet6Address ip = (Inet6Address) la.getAddress(); Loading @@ -1455,22 +1484,40 @@ public class IpClient extends StateMachine { return null; } private void maybeSendGratuitousNAs(final LinkProperties newLp) { if (!newLp.hasGlobalIpv6Address() || !isGratuitousNaEnabled()) return; private void maybeSendGratuitousNAs(final LinkProperties lp, boolean afterRoaming) { if (!lp.hasGlobalIpv6Address()) return; final Inet6Address src = getIpv6LinkLocalAddress(newLp); if (src == null) return; for (LinkAddress la : newLp.getLinkAddresses()) { final Inet6Address srcIp = getIpv6LinkLocalAddress(lp); if (srcIp == null) return; // TODO: add experiment with sending only one gratuitous NA packet instead of one // packet per address. for (LinkAddress la : lp.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; // Already sent gratuitous NA with this target global IPv6 address. But for // the L2 roaming case, device should always (re)transmit Gratuitous NA for // each IPv6 global unicast address respectively after roaming. if (!afterRoaming && mGratuitousNaTargetAddresses.contains(targetIp)) continue; if (DBG) { Log.d(mTag, "send Gratuitous NA from " + src + ", target Address is " + targetIp); mLog.log("send Gratuitous NA from " + srcIp.getHostAddress() + " for " + targetIp.getHostAddress() + (afterRoaming ? " after roaming" : "")); } sendGratuitousNA(srcIp, targetIp); if (!afterRoaming) mGratuitousNaTargetAddresses.add(targetIp); } } private void maybeSendGratuitousARP(final LinkProperties lp) { for (LinkAddress address : lp.getLinkAddresses()) { if (address.getAddress() instanceof Inet4Address) { final Inet4Address srcIp = (Inet4Address) address.getAddress(); if (DBG) { mLog.log("send GARP for " + srcIp.getHostAddress() + " HW address: " + mInterfaceParams.macAddr); } sendGratuitousARP(srcIp); } sendGratuitousNA(src, targetIp); mGratuitousNaTargetAddresses.add(targetIp); } } Loading @@ -1484,7 +1531,9 @@ public class IpClient extends StateMachine { // 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); if (isGratuitousNaEnabled()) { maybeSendGratuitousNAs(newLp, false /* isGratuitousNaAfterRoaming */); } // Either success IPv4 or IPv6 provisioning triggers new LinkProperties update, // wait for the provisioning completion and record the latency. Loading Loading @@ -1741,6 +1790,13 @@ public class IpClient extends StateMachine { // If the BSSID has not changed, there is nothing to do. if (info.bssid.equals(mCurrentBssid)) return; // Before trigger probing to the interesting neighbors, send Gratuitous ARP // and Neighbor Advertisment in advance to propgate host's IPv4/v6 addresses. if (isGratuitousArpNaRoamingEnabled()) { maybeSendGratuitousARP(mLinkProperties); maybeSendGratuitousNAs(mLinkProperties, true /* isGratuitousNaAfterRoaming */); } if (mIpReachabilityMonitor != null) { mIpReachabilityMonitor.probeAll(); } Loading
src/android/net/util/NetworkStackUtils.java +7 −0 Original line number Diff line number Diff line Loading @@ -242,6 +242,13 @@ public class NetworkStackUtils { */ public static final String IPCLIENT_GRATUITOUS_NA_VERSION = "ipclient_gratuitous_na_version"; /** * Experiment flag to enable sending Gratuitous APR and Gratuitous Neighbor Advertisement for * all assigned IPv4 and IPv6 GUAs after completing L2 roaming. */ public static final String IPCLIENT_GARP_NA_ROAMING_VERSION = "ipclient_garp_na_roaming_version"; static { System.loadLibrary("networkstackutilsjni"); } Loading
tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java +146 −25 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4 import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY; import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST; import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN; import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST; 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; Loading Loading @@ -1121,6 +1122,14 @@ public abstract class IpClientIntegrationTestCommon { assertEquals(packet.senderIp, CLIENT_ADDR); } private void assertGratuitousARP(final ArpPacket packet) { assertEquals(packet.opCode, ARP_REPLY); assertEquals(packet.senderIp, CLIENT_ADDR); assertEquals(packet.targetIp, CLIENT_ADDR); assertTrue(Arrays.equals(packet.senderHwAddress.toByteArray(), mClientMac)); assertTrue(Arrays.equals(packet.targetHwAddress.toByteArray(), ETHER_BROADCAST)); } private void doIpAddressConflictDetectionTest(final boolean causeIpAddressConflict, final boolean shouldReplyRapidCommitAck, final boolean isDhcpIpConflictDetectEnabled, final boolean shouldResponseArpReply) throws Exception { Loading Loading @@ -1537,6 +1546,16 @@ public abstract class IpClientIntegrationTestCommon { return addr.isGlobalPreferred() && hasFlag(addr, flag); } private LinkProperties doIpv6OnlyProvisioning() throws Exception { 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); return doIpv6OnlyProvisioning(inOrder, ra); } private LinkProperties doIpv6OnlyProvisioning(InOrder inOrder, ByteBuffer ra) throws Exception { waitForRouterSolicitation(); mPacketReader.sendResponse(ra); Loading Loading @@ -2234,6 +2253,14 @@ public abstract class IpClientIntegrationTestCommon { true /* expectMetered */); } private void forceLayer2Roaming() throws Exception { final Layer2InformationParcelable roamingInfo = new Layer2InformationParcelable(); roamingInfo.bssid = MacAddress.fromString(TEST_DHCP_ROAM_BSSID); roamingInfo.l2Key = TEST_DHCP_ROAM_L2KEY; roamingInfo.cluster = TEST_DHCP_ROAM_CLUSTER; mIIpClient.updateLayer2Information(roamingInfo); } private void doDhcpRoamingTest(final boolean hasMismatchedIpAddress, final String displayName, final String ssid, final String bssid, final boolean expectRoaming) throws Exception { long currentTime = System.currentTimeMillis(); Loading @@ -2260,11 +2287,7 @@ public abstract class IpClientIntegrationTestCommon { assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU); // simulate the roaming by updating bssid. final Layer2InformationParcelable roamingInfo = new Layer2InformationParcelable(); roamingInfo.bssid = MacAddress.fromString(TEST_DHCP_ROAM_BSSID); roamingInfo.l2Key = TEST_DHCP_ROAM_L2KEY; roamingInfo.cluster = TEST_DHCP_ROAM_CLUSTER; mIpc.updateLayer2Information(roamingInfo); forceLayer2Roaming(); currentTime = System.currentTimeMillis(); reset(mIpMemoryStore); Loading Loading @@ -2338,18 +2361,7 @@ public abstract class IpClientIntegrationTestCommon { TEST_DHCP_ROAM_SSID, TEST_DEFAULT_BSSID, true /* expectRoaming */); } private void doDualStackProvisioning() throws Exception { when(mCm.shouldAvoidBadWifi()).thenReturn(true); final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() .build(); // Enable rapid commit to accelerate DHCP handshake to shorten test duration, // not strictly necessary. setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); mIpc.startProvisioning(config); private void performDualStackProvisioning() throws Exception { final InOrder inOrder = inOrder(mCb); final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>(); final String dnsServer = "2001:4860:4860::64"; Loading @@ -2376,6 +2388,21 @@ public abstract class IpClientIntegrationTestCommon { reset(mCb); } private void doDualStackProvisioning() throws Exception { when(mCm.shouldAvoidBadWifi()).thenReturn(true); final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() .build(); // Enable rapid commit to accelerate DHCP handshake to shorten test duration, // not strictly necessary. setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); mIpc.startProvisioning(config); performDualStackProvisioning(); } @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required") public void testIgnoreIpv6ProvisioningLoss() throws Exception { doDualStackProvisioning(); Loading Loading @@ -2796,7 +2823,7 @@ public abstract class IpClientIntegrationTestCommon { } @Test public void testGratuitousNa() throws Exception { public void testGratuitousNaForNewGlobalUnicastAddresses() throws Exception { final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() .withoutIPv4() Loading @@ -2807,13 +2834,7 @@ public abstract class IpClientIntegrationTestCommon { 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); doIpv6OnlyProvisioning(); final List<NeighborAdvertisement> naList = new ArrayList<>(); NeighborAdvertisement packet; Loading @@ -2823,4 +2844,104 @@ public abstract class IpClientIntegrationTestCommon { } assertEquals(2, naList.size()); // privacy address and stable privacy address } private void startGratuitousArpAndNaAfterRoamingTest(boolean isGratuitousArpNaRoamingEnabled, boolean hasIpv4, boolean hasIpv6) throws Exception { final ScanResultInfo scanResultInfo = makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID); final ProvisioningConfiguration.Builder prov = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() .withScanResultInfo(scanResultInfo) .withDisplayName("ssid"); if (!hasIpv4) prov.withoutIPv4(); if (!hasIpv6) prov.withoutIPv6(); // Enable rapid commit to accelerate DHCP handshake to shorten test duration, // not strictly necessary. setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */); if (isGratuitousArpNaRoamingEnabled) { setFeatureEnabled(NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION, true); assertTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION, false)); } startIpClientProvisioning(prov.build()); } private void waitForGratuitousArpAndNaPacket(final List<ArpPacket> arpList, final List<NeighborAdvertisement> naList) throws Exception { NeighborAdvertisement na; ArpPacket garp; do { na = getNextNeighborAdvertisement(); if (na != null) { assertGratuitousNa(na); naList.add(na); } garp = getNextArpPacket(TEST_TIMEOUT_MS); if (garp != null) { assertGratuitousARP(garp); arpList.add(garp); } } while (na != null || garp != null); } @Test public void testGratuitousArpAndNaAfterRoaming() throws Exception { startGratuitousArpAndNaAfterRoamingTest(true /* isGratuitousArpNaRoamingEnabled */, true /* hasIpv4 */, true /* hasIpv6 */); performDualStackProvisioning(); forceLayer2Roaming(); final List<ArpPacket> arpList = new ArrayList<>(); final List<NeighborAdvertisement> naList = new ArrayList<>(); waitForGratuitousArpAndNaPacket(arpList, naList); assertEquals(2, naList.size()); // privacy address and stable privacy address assertEquals(1, arpList.size()); // IPv4 address } @Test public void testGratuitousArpAndNaAfterRoaming_disableExpFlag() throws Exception { startGratuitousArpAndNaAfterRoamingTest(false /* isGratuitousArpNaRoamingEnabled */, true /* hasIpv6 */, true /* hasIpv6 */); performDualStackProvisioning(); forceLayer2Roaming(); final List<ArpPacket> arpList = new ArrayList<>(); final List<NeighborAdvertisement> naList = new ArrayList<>(); waitForGratuitousArpAndNaPacket(arpList, naList); assertEquals(0, naList.size()); assertEquals(0, arpList.size()); } @Test public void testGratuitousArpAndNaAfterRoaming_IPv6OnlyNetwork() throws Exception { startGratuitousArpAndNaAfterRoamingTest(true /* isGratuitousArpNaRoamingEnabled */, false /* hasIpv4 */, true /* hasIpv6 */); doIpv6OnlyProvisioning(); forceLayer2Roaming(); final List<ArpPacket> arpList = new ArrayList<>(); final List<NeighborAdvertisement> naList = new ArrayList<>(); waitForGratuitousArpAndNaPacket(arpList, naList); assertEquals(2, naList.size()); assertEquals(0, arpList.size()); } @Test public void testGratuitousArpAndNaAfterRoaming_IPv4OnlyNetwork() throws Exception { startGratuitousArpAndNaAfterRoamingTest(true /* isGratuitousArpNaRoamingEnabled */, true /* hasIpv4 */, false /* hasIpv6 */); // Start IPv4 provisioning and wait until entire provisioning completes. handleDhcpPackets(true /* isSuccessLease */, TEST_LEASE_DURATION_S, true /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, null /* serverSentUrl */); verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR)); forceLayer2Roaming(); final List<ArpPacket> arpList = new ArrayList<>(); final List<NeighborAdvertisement> naList = new ArrayList<>(); waitForGratuitousArpAndNaPacket(arpList, naList); assertEquals(0, naList.size()); assertEquals(1, arpList.size()); } }