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

Commit e5ec4d00 authored by Xiao Ma's avatar Xiao Ma Committed by Gerrit Code Review
Browse files

Merge "Send GARP and unsolicited multicast NA after L2 roaming."

parents 040b3972 8a0b767a
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());
    }
}