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

Commit 91f0eaea authored by Xiao Ma's avatar Xiao Ma Committed by Automerger Merge Worker
Browse files

Merge "Support gratuitous unsolicited Neighbor Advertisement." am: 00197139...

Merge "Support gratuitous unsolicited Neighbor Advertisement." am: 00197139 am: 7e9ee219 am: fdf4155a am: 97fba1f4

Original change: https://android-review.googlesource.com/c/platform/packages/modules/NetworkStack/+/1535654

Change-Id: Ie8addc507a61e1230acd571d527aa2a3f2355e88
parents 4fd34b0e 97fba1f4
Loading
Loading
Loading
Loading
+103 −0
Original line number Original line Diff line number Diff line
@@ -18,8 +18,14 @@ package android.net.ip;


import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable;
import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable;
import static android.net.util.NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static android.system.OsConstants.AF_PACKET;
import static android.system.OsConstants.ETH_P_IPV6;
import static android.system.OsConstants.SOCK_NONBLOCK;
import static android.system.OsConstants.SOCK_RAW;


import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
import static com.android.net.module.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID;
import static com.android.net.module.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID;
import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission;
import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission;


@@ -52,6 +58,7 @@ import android.net.shared.ProvisioningConfiguration;
import android.net.shared.ProvisioningConfiguration.ScanResultInfo;
import android.net.shared.ProvisioningConfiguration.ScanResultInfo;
import android.net.shared.ProvisioningConfiguration.ScanResultInfo.InformationElement;
import android.net.shared.ProvisioningConfiguration.ScanResultInfo.InformationElement;
import android.net.util.InterfaceParams;
import android.net.util.InterfaceParams;
import android.net.util.NetworkStackUtils;
import android.net.util.SharedLog;
import android.net.util.SharedLog;
import android.os.Build;
import android.os.Build;
import android.os.ConditionVariable;
import android.os.ConditionVariable;
@@ -61,6 +68,8 @@ import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.os.SystemClock;
import android.stats.connectivity.DisconnectCode;
import android.stats.connectivity.DisconnectCode;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
import android.text.TextUtils;
import android.util.LocalLog;
import android.util.LocalLog;
import android.util.Log;
import android.util.Log;
@@ -79,16 +88,21 @@ import com.android.internal.util.StateMachine;
import com.android.internal.util.WakeupMessage;
import com.android.internal.util.WakeupMessage;
import com.android.net.module.util.DeviceConfigUtils;
import com.android.net.module.util.DeviceConfigUtils;
import com.android.networkstack.apishim.NetworkInformationShimImpl;
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.NetworkInformationShim;
import com.android.networkstack.apishim.common.ShimUtils;
import com.android.networkstack.apishim.common.ShimUtils;
import com.android.networkstack.metrics.IpProvisioningMetrics;
import com.android.networkstack.metrics.IpProvisioningMetrics;
import com.android.networkstack.packets.NeighborAdvertisement;
import com.android.server.NetworkObserverRegistry;
import com.android.server.NetworkObserverRegistry;
import com.android.server.NetworkStackService.NetworkStackServiceManager;
import com.android.server.NetworkStackService.NetworkStackServiceManager;


import java.io.FileDescriptor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.PrintWriter;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.MalformedURLException;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.URL;
import java.net.URL;
import java.nio.BufferUnderflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteBuffer;
@@ -479,6 +493,8 @@ public class IpClient extends StateMachine {
    private final MessageHandlingLogger mMsgStateLogger;
    private final MessageHandlingLogger mMsgStateLogger;
    private final IpConnectivityLog mMetricsLog;
    private final IpConnectivityLog mMetricsLog;
    private final InterfaceController mInterfaceCtrl;
    private final InterfaceController mInterfaceCtrl;
    // Set of IPv6 addresses for which unsolicited gratuitous NA packets have been sent.
    private final Set<Inet6Address> mGratuitousNaTargetAddresses = new HashSet<>();


    // Ignore nonzero RDNSS option lifetimes below this value. 0 = disabled.
    // Ignore nonzero RDNSS option lifetimes below this value. 0 = disabled.
    private final int mMinRdnssLifetimeSec;
    private final int mMinRdnssLifetimeSec;
@@ -564,6 +580,16 @@ public class IpClient extends StateMachine {
        public IpConnectivityLog getIpConnectivityLog() {
        public IpConnectivityLog getIpConnectivityLog() {
            return new IpConnectivityLog();
            return new IpConnectivityLog();
        }
        }

        /**
         * Return whether a feature guarded by a feature flag is enabled.
         * @see NetworkStackUtils#isFeatureEnabled(Context, String, String)
         */
        public boolean isFeatureEnabled(final Context context, final String name,
                boolean defaultEnabled) {
            return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name,
                    defaultEnabled);
        }
    }
    }


    public IpClient(Context context, String ifName, IIpClientCallbacks callback,
    public IpClient(Context context, String ifName, IIpClientCallbacks callback,
@@ -647,6 +673,21 @@ public class IpClient extends StateMachine {
                logMsg(msg);
                logMsg(msg);
            }
            }


            @Override
            public void onInterfaceAddressRemoved(LinkAddress address, String iface) {
                super.onInterfaceAddressRemoved(address, iface);
                if (!mInterfaceName.equals(iface)) return;
                if (!address.isIpv6()) return;
                final Inet6Address targetIp = (Inet6Address) address.getAddress();
                if (mGratuitousNaTargetAddresses.contains(targetIp)) {
                    mGratuitousNaTargetAddresses.remove(targetIp);

                    final String msg = "Global IPv6 address: " + targetIp
                            + " has removed from the set of gratuitous NA target address.";
                    logMsg(msg);
                }
            }

            private void logMsg(String msg) {
            private void logMsg(String msg) {
                Log.d(mTag, msg);
                Log.d(mTag, msg);
                getHandler().post(() -> mLog.log("OBSERVED " + msg));
                getHandler().post(() -> mLog.log("OBSERVED " + msg));
@@ -793,6 +834,11 @@ public class IpClient extends StateMachine {
        mLinkObserver.shutdown();
        mLinkObserver.shutdown();
    }
    }


    private boolean isGratuitousNaEnabled() {
        return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GRATUITOUS_NA_VERSION,
                false /* defaultEnabled */);
    }

    @Override
    @Override
    protected void onQuitting() {
    protected void onQuitting() {
        mCallback.onQuit();
        mCallback.onQuit();
@@ -1377,6 +1423,57 @@ 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);
        FileDescriptor sock = null;
        try {
            sock = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0 /* protocol */);
            final SocketAddress sockAddress =
                    SocketUtilsShimImpl.newInstance().makePacketSocketAddress(ETH_P_IPV6,
                            mInterfaceParams.index, dstMac.toByteArray());
            Os.sendto(sock, packet.array(), 0 /* byteOffset */, packet.limit() /* byteCount */,
                    0 /* flags */, sockAddress);
        } catch (SocketException | ErrnoException e) {
            logError("Fail to send Gratuitous Neighbor Advertisement", e);
        } finally {
            NetworkStackUtils.closeSocketQuietly(sock);
        }
    }

    private Inet6Address getIpv6LinkLocalAddress(final LinkProperties newLp) {
        Inet6Address src = null;
        for (LinkAddress la : newLp.getLinkAddresses()) {
            if (!la.isIpv6()) continue;
            final Inet6Address ip = (Inet6Address) la.getAddress();
            if (ip.isLinkLocalAddress()) return ip;
        }
        return null;
    }

    private void maybeSendGratuitousNAs(final LinkProperties newLp) {
        if (!newLp.hasGlobalIpv6Address() || !isGratuitousNaEnabled()) return;

        final Inet6Address src = getIpv6LinkLocalAddress(newLp);
        if (src == null) return;
        for (LinkAddress la : newLp.getLinkAddresses()) {
            if (!la.isIpv6() || !la.isGlobalPreferred()) continue;
            final Inet6Address targetIp = (Inet6Address) la.getAddress();
            // Already sent gratuitous NA with this target global IPv6 address.
            if (mGratuitousNaTargetAddresses.contains(targetIp)) continue;
            if (DBG) {
                Log.d(mTag, "send Gratuitous NA from " + src + ", target Address is "
                            + targetIp);
            }
            sendGratuitousNA(src, targetIp);
            mGratuitousNaTargetAddresses.add(targetIp);
        }
    }

    // Returns false if we have lost provisioning, true otherwise.
    // Returns false if we have lost provisioning, true otherwise.
    private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
    private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
        final LinkProperties newLp = assembleLinkProperties();
        final LinkProperties newLp = assembleLinkProperties();
@@ -1384,6 +1481,11 @@ public class IpClient extends StateMachine {
            return true;
            return true;
        }
        }


        // Check if new assigned IPv6 GUA is available in the LinkProperties now. If so, initiate
        // gratuitous multicast unsolicited Neighbor Advertisements as soon as possible to inform
        // first-hop routers that the new GUA host is goning to use.
        maybeSendGratuitousNAs(newLp);

        // Either success IPv4 or IPv6 provisioning triggers new LinkProperties update,
        // Either success IPv4 or IPv6 provisioning triggers new LinkProperties update,
        // wait for the provisioning completion and record the latency.
        // wait for the provisioning completion and record the latency.
        mIpProvisioningMetrics.setIPv4ProvisionedLatencyOnFirstTime(newLp.isIpv4Provisioned());
        mIpProvisioningMetrics.setIPv4ProvisionedLatencyOnFirstTime(newLp.isIpv4Provisioned());
@@ -1662,6 +1764,7 @@ public class IpClient extends StateMachine {
        public void enter() {
        public void enter() {
            stopAllIP();
            stopAllIP();
            mHasDisabledIPv6OnProvLoss = false;
            mHasDisabledIPv6OnProvLoss = false;
            mGratuitousNaTargetAddresses.clear();


            mLinkObserver.clearInterfaceParams();
            mLinkObserver.clearInterfaceParams();
            resetLinkProperties();
            resetLinkProperties();
+25 −0
Original line number Original line Diff line number Diff line
@@ -17,12 +17,16 @@
package android.net.util;
package android.net.util;


import android.content.Context;
import android.content.Context;
import android.net.MacAddress;

import androidx.annotation.NonNull;


import com.android.net.module.util.DeviceConfigUtils;
import com.android.net.module.util.DeviceConfigUtils;


import java.io.FileDescriptor;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.SocketException;
import java.net.SocketException;


/**
/**
@@ -232,6 +236,12 @@ public class NetworkStackUtils {
     */
     */
    public static final String VALIDATION_METRICS_VERSION = "validation_metrics_version";
    public static final String VALIDATION_METRICS_VERSION = "validation_metrics_version";


    /**
     * Experiment flag to enable sending gratuitous multicast unsolicited Neighbor Advertisements
     * to propagate new assigned IPv6 GUA as quickly as possible.
     */
    public static final String IPCLIENT_GRATUITOUS_NA_VERSION = "ipclient_gratuitous_na_version";

    static {
    static {
        System.loadLibrary("networkstackutilsjni");
        System.loadLibrary("networkstackutilsjni");
    }
    }
@@ -246,6 +256,21 @@ public class NetworkStackUtils {
        }
        }
    }
    }


    /**
     * Convert IPv6 multicast address to ethernet multicast address in network order.
     */
    public static MacAddress ipv6MulticastToEthernetMulticast(@NonNull final Inet6Address addr) {
        final byte[] etherMulticast = new byte[6];
        final byte[] ipv6Multicast = addr.getAddress();
        etherMulticast[0] = (byte) 0x33;
        etherMulticast[1] = (byte) 0x33;
        etherMulticast[2] = ipv6Multicast[12];
        etherMulticast[3] = ipv6Multicast[13];
        etherMulticast[4] = ipv6Multicast[14];
        etherMulticast[5] = ipv6Multicast[15];
        return MacAddress.fromBytes(etherMulticast);
    }

    /**
    /**
     * Attaches a socket filter that accepts DHCP packets to the given socket.
     * Attaches a socket filter that accepts DHCP packets to the given socket.
     */
     */
+79 −1
Original line number Original line Diff line number Diff line
@@ -39,11 +39,15 @@ 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_ADDR_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY;
import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY;
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS;
import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK;


import static junit.framework.Assert.fail;
import static junit.framework.Assert.fail;


@@ -141,6 +145,7 @@ import com.android.networkstack.apishim.ConstantsShim;
import com.android.networkstack.apishim.common.ShimUtils;
import com.android.networkstack.apishim.common.ShimUtils;
import com.android.networkstack.arp.ArpPacket;
import com.android.networkstack.arp.ArpPacket;
import com.android.networkstack.metrics.IpProvisioningMetrics;
import com.android.networkstack.metrics.IpProvisioningMetrics;
import com.android.networkstack.packets.NeighborAdvertisement;
import com.android.server.NetworkObserver;
import com.android.server.NetworkObserver;
import com.android.server.NetworkObserverRegistry;
import com.android.server.NetworkObserverRegistry;
import com.android.server.NetworkStackService.NetworkStackServiceManager;
import com.android.server.NetworkStackService.NetworkStackServiceManager;
@@ -390,6 +395,7 @@ public abstract class IpClientIntegrationTestCommon {
            return mDhcpClient;
            return mDhcpClient;
        }
        }


        @Override
        public boolean isFeatureEnabled(final Context context, final String name,
        public boolean isFeatureEnabled(final Context context, final String name,
                final boolean defaultEnabled) {
                final boolean defaultEnabled) {
            return IpClientIntegrationTestCommon.this.isFeatureEnabled(name, defaultEnabled);
            return IpClientIntegrationTestCommon.this.isFeatureEnabled(name, defaultEnabled);
@@ -688,6 +694,14 @@ public abstract class IpClientIntegrationTestCommon {
        }
        }
    }
    }


    private NeighborAdvertisement parseNeighborAdvertisementOrNull(final byte[] packet) {
        try {
            return NeighborAdvertisement.parse(packet, packet.length);
        } catch (NeighborAdvertisement.ParseException e) {
            return null;
        }
    }

    private static ByteBuffer buildDhcpOfferPacket(final DhcpPacket packet,
    private static ByteBuffer buildDhcpOfferPacket(final DhcpPacket packet,
            final Inet4Address clientAddress, final Integer leaseTimeSec, final short mtu,
            final Inet4Address clientAddress, final Integer leaseTimeSec, final short mtu,
            final String captivePortalUrl, final Integer ipv6OnlyWaitTime) {
            final String captivePortalUrl, final Integer ipv6OnlyWaitTime) {
@@ -1418,6 +1432,24 @@ public abstract class IpClientIntegrationTestCommon {
                        == (byte) ICMPV6_ROUTER_SOLICITATION;
                        == (byte) ICMPV6_ROUTER_SOLICITATION;
    }
    }


    private boolean isNeighborAdvertisement(final byte[] packetBytes) {
        ByteBuffer packet = ByteBuffer.wrap(packetBytes);
        return packet.getShort(ETHER_TYPE_OFFSET) == (short) ETH_P_IPV6
                && packet.get(ETHER_HEADER_LEN + IPV6_PROTOCOL_OFFSET) == (byte) IPPROTO_ICMPV6
                && packet.get(ETHER_HEADER_LEN + IPV6_HEADER_LEN)
                        == (byte) ICMPV6_NEIGHBOR_ADVERTISEMENT;
    }

    private NeighborAdvertisement getNextNeighborAdvertisement() throws ParseException {
        final byte[] packet = mPacketReader.popPacket(PACKET_TIMEOUT_MS,
                this::isNeighborAdvertisement);
        if (packet == null) return null;

        final NeighborAdvertisement na = parseNeighborAdvertisementOrNull(packet);
        assertNotNull("Invalid neighbour advertisement received", na);
        return na;
    }

    private void waitForRouterSolicitation() throws ParseException {
    private void waitForRouterSolicitation() throws ParseException {
        assertNotNull("No router solicitation received on interface within timeout",
        assertNotNull("No router solicitation received on interface within timeout",
                mPacketReader.popPacket(PACKET_TIMEOUT_MS, this::isRouterSolicitation));
                mPacketReader.popPacket(PACKET_TIMEOUT_MS, this::isRouterSolicitation));
@@ -1448,7 +1480,7 @@ public abstract class IpClientIntegrationTestCommon {
    private static ByteBuffer buildPioOption(int valid, int preferred, String prefixString)
    private static ByteBuffer buildPioOption(int valid, int preferred, String prefixString)
            throws Exception {
            throws Exception {
        return PrefixInformationOption.build(new IpPrefix(prefixString),
        return PrefixInformationOption.build(new IpPrefix(prefixString),
                (byte) 0b11000000 /* L = 1, A = 1 */, valid, preferred);
                (byte) (PIO_FLAG_ON_LINK | PIO_FLAG_AUTONOMOUS), valid, preferred);
    }
    }


    private static ByteBuffer buildRdnssOption(int lifetime, String... servers) throws Exception {
    private static ByteBuffer buildRdnssOption(int lifetime, String... servers) throws Exception {
@@ -2744,4 +2776,50 @@ public abstract class IpClientIntegrationTestCommon {
        assertArrayEquals(packet.mUserClass, TEST_OEM_USER_CLASS_INFO);
        assertArrayEquals(packet.mUserClass, TEST_OEM_USER_CLASS_INFO);
        assertFalse(packet.hasRequestedParam((byte) 42 /* NTP_SERVER */));
        assertFalse(packet.hasRequestedParam((byte) 42 /* NTP_SERVER */));
    }
    }

    private void assertGratuitousNa(final NeighborAdvertisement na) throws Exception {
        final MacAddress etherMulticast =
                NetworkStackUtils.ipv6MulticastToEthernetMulticast(IPV6_ADDR_ALL_ROUTERS_MULTICAST);
        final LinkAddress target = new LinkAddress(na.naHdr.target, 64);

        assertEquals(etherMulticast, na.ethHdr.dstMac);
        assertEquals(ETH_P_IPV6, na.ethHdr.etherType);
        assertEquals(IPPROTO_ICMPV6, na.ipv6Hdr.nextHeader);
        assertEquals(0xff, na.ipv6Hdr.hopLimit);
        assertTrue(na.ipv6Hdr.srcIp.isLinkLocalAddress());
        assertEquals(IPV6_ADDR_ALL_ROUTERS_MULTICAST, na.ipv6Hdr.dstIp);
        assertEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, na.icmpv6Hdr.type);
        assertEquals(0, na.icmpv6Hdr.code);
        assertEquals(0, na.naHdr.flags);
        assertTrue(target.isGlobalPreferred());
    }

    @Test
    public void testGratuitousNa() throws Exception {
        final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
                .withoutIpReachabilityMonitor()
                .withoutIPv4()
                .build();

        setFeatureEnabled(NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION,
                true /* isGratuitousNaEnabled */);
        assertTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION, false));
        startIpClientProvisioning(config);

        final InOrder inOrder = inOrder(mCb);
        final String dnsServer = "2001:4860:4860::64";
        final ByteBuffer pio = buildPioOption(3600, 1800, "2001:db8:1::/64");
        final ByteBuffer rdnss = buildRdnssOption(3600, dnsServer);
        final ByteBuffer ra = buildRaPacket(pio, rdnss);

        doIpv6OnlyProvisioning(inOrder, ra);

        final List<NeighborAdvertisement> naList = new ArrayList<>();
        NeighborAdvertisement packet;
        while ((packet = getNextNeighborAdvertisement()) != null) {
            assertGratuitousNa(packet);
            naList.add(packet);
        }
        assertEquals(2, naList.size()); // privacy address and stable privacy address
    }
}
}