Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8a0b767a authored by Xiao Ma's avatar Xiao Ma
Browse files

Send GARP and unsolicited multicast NA after L2 roaming.

Bug: 80379409
Bug: 162944199
Test: m NetworkStackIntegrationTest
Change-Id: I024547c2510f529a856b09cdfa5aad231670fd20
parent 01e4fa96
Loading
Loading
Loading
Loading
+81 −25
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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();
@@ -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();
@@ -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);
        }
    }

@@ -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.
@@ -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();
        }
+7 −0
Original line number Diff line number Diff line
@@ -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");
    }
+146 −25
Original line number Diff line number Diff line
@@ -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;
@@ -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 {
@@ -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);
@@ -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();
@@ -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);
@@ -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";
@@ -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();
@@ -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()
@@ -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;
@@ -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());
    }
}