Loading src/android/net/ip/IpClient.java +103 −0 Original line number Diff line number Diff line Loading @@ -18,8 +18,14 @@ 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_GRATUITOUS_NA_VERSION; 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.server.util.PermissionUtil.enforceNetworkStackCallingPermission; Loading Loading @@ -52,6 +58,7 @@ import android.net.shared.ProvisioningConfiguration; import android.net.shared.ProvisioningConfiguration.ScanResultInfo; import android.net.shared.ProvisioningConfiguration.ScanResultInfo.InformationElement; import android.net.util.InterfaceParams; import android.net.util.NetworkStackUtils; import android.net.util.SharedLog; import android.os.Build; import android.os.ConditionVariable; Loading @@ -61,6 +68,8 @@ import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.SystemClock; import android.stats.connectivity.DisconnectCode; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; import android.util.LocalLog; import android.util.Log; Loading @@ -79,16 +88,21 @@ import com.android.internal.util.StateMachine; import com.android.internal.util.WakeupMessage; import com.android.net.module.util.DeviceConfigUtils; 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.metrics.IpProvisioningMetrics; import com.android.networkstack.packets.NeighborAdvertisement; import com.android.server.NetworkObserverRegistry; import com.android.server.NetworkStackService.NetworkStackServiceManager; import java.io.FileDescriptor; import java.io.PrintWriter; import java.net.Inet6Address; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.SocketAddress; import java.net.SocketException; import java.net.URL; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; Loading Loading @@ -479,6 +493,8 @@ public class IpClient extends StateMachine { private final MessageHandlingLogger mMsgStateLogger; private final IpConnectivityLog mMetricsLog; 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. private final int mMinRdnssLifetimeSec; Loading Loading @@ -564,6 +580,16 @@ public class IpClient extends StateMachine { public IpConnectivityLog getIpConnectivityLog() { 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, Loading Loading @@ -647,6 +673,21 @@ public class IpClient extends StateMachine { 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) { Log.d(mTag, msg); getHandler().post(() -> mLog.log("OBSERVED " + msg)); Loading Loading @@ -793,6 +834,11 @@ public class IpClient extends StateMachine { mLinkObserver.shutdown(); } private boolean isGratuitousNaEnabled() { return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GRATUITOUS_NA_VERSION, false /* defaultEnabled */); } @Override protected void onQuitting() { mCallback.onQuit(); Loading Loading @@ -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. private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) { final LinkProperties newLp = assembleLinkProperties(); Loading @@ -1384,6 +1481,11 @@ public class IpClient extends StateMachine { 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, // wait for the provisioning completion and record the latency. mIpProvisioningMetrics.setIPv4ProvisionedLatencyOnFirstTime(newLp.isIpv4Provisioned()); Loading Loading @@ -1662,6 +1764,7 @@ public class IpClient extends StateMachine { public void enter() { stopAllIP(); mHasDisabledIPv6OnProvLoss = false; mGratuitousNaTargetAddresses.clear(); mLinkObserver.clearInterfaceParams(); resetLinkProperties(); Loading src/android/net/util/NetworkStackUtils.java +25 −0 Original line number Diff line number Diff line Loading @@ -17,12 +17,16 @@ package android.net.util; import android.content.Context; import android.net.MacAddress; import androidx.annotation.NonNull; import com.android.net.module.util.DeviceConfigUtils; import java.io.FileDescriptor; import java.io.IOException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.SocketException; /** Loading Loading @@ -232,6 +236,12 @@ public class NetworkStackUtils { */ 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 { System.loadLibrary("networkstackutilsjni"); } Loading @@ -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. */ Loading tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java +79 −1 Original line number Diff line number Diff line Loading @@ -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_HEADER_LEN; 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.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_ROUTERS_MULTICAST; import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN; import static com.android.net.module.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET; import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS; import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK; import static junit.framework.Assert.fail; Loading Loading @@ -141,6 +145,7 @@ import com.android.networkstack.apishim.ConstantsShim; 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.NetworkObserver; import com.android.server.NetworkObserverRegistry; import com.android.server.NetworkStackService.NetworkStackServiceManager; Loading Loading @@ -390,6 +395,7 @@ public abstract class IpClientIntegrationTestCommon { return mDhcpClient; } @Override public boolean isFeatureEnabled(final Context context, final String name, final boolean defaultEnabled) { return IpClientIntegrationTestCommon.this.isFeatureEnabled(name, defaultEnabled); Loading Loading @@ -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, final Inet4Address clientAddress, final Integer leaseTimeSec, final short mtu, final String captivePortalUrl, final Integer ipv6OnlyWaitTime) { Loading Loading @@ -1418,6 +1432,24 @@ public abstract class IpClientIntegrationTestCommon { == (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 { assertNotNull("No router solicitation received on interface within timeout", mPacketReader.popPacket(PACKET_TIMEOUT_MS, this::isRouterSolicitation)); Loading Loading @@ -1448,7 +1480,7 @@ public abstract class IpClientIntegrationTestCommon { private static ByteBuffer buildPioOption(int valid, int preferred, String prefixString) throws Exception { 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 { Loading Loading @@ -2744,4 +2776,50 @@ public abstract class IpClientIntegrationTestCommon { assertArrayEquals(packet.mUserClass, TEST_OEM_USER_CLASS_INFO); 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 } } Loading
src/android/net/ip/IpClient.java +103 −0 Original line number Diff line number Diff line Loading @@ -18,8 +18,14 @@ 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_GRATUITOUS_NA_VERSION; 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.server.util.PermissionUtil.enforceNetworkStackCallingPermission; Loading Loading @@ -52,6 +58,7 @@ import android.net.shared.ProvisioningConfiguration; import android.net.shared.ProvisioningConfiguration.ScanResultInfo; import android.net.shared.ProvisioningConfiguration.ScanResultInfo.InformationElement; import android.net.util.InterfaceParams; import android.net.util.NetworkStackUtils; import android.net.util.SharedLog; import android.os.Build; import android.os.ConditionVariable; Loading @@ -61,6 +68,8 @@ import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.SystemClock; import android.stats.connectivity.DisconnectCode; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; import android.util.LocalLog; import android.util.Log; Loading @@ -79,16 +88,21 @@ import com.android.internal.util.StateMachine; import com.android.internal.util.WakeupMessage; import com.android.net.module.util.DeviceConfigUtils; 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.metrics.IpProvisioningMetrics; import com.android.networkstack.packets.NeighborAdvertisement; import com.android.server.NetworkObserverRegistry; import com.android.server.NetworkStackService.NetworkStackServiceManager; import java.io.FileDescriptor; import java.io.PrintWriter; import java.net.Inet6Address; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.SocketAddress; import java.net.SocketException; import java.net.URL; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; Loading Loading @@ -479,6 +493,8 @@ public class IpClient extends StateMachine { private final MessageHandlingLogger mMsgStateLogger; private final IpConnectivityLog mMetricsLog; 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. private final int mMinRdnssLifetimeSec; Loading Loading @@ -564,6 +580,16 @@ public class IpClient extends StateMachine { public IpConnectivityLog getIpConnectivityLog() { 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, Loading Loading @@ -647,6 +673,21 @@ public class IpClient extends StateMachine { 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) { Log.d(mTag, msg); getHandler().post(() -> mLog.log("OBSERVED " + msg)); Loading Loading @@ -793,6 +834,11 @@ public class IpClient extends StateMachine { mLinkObserver.shutdown(); } private boolean isGratuitousNaEnabled() { return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GRATUITOUS_NA_VERSION, false /* defaultEnabled */); } @Override protected void onQuitting() { mCallback.onQuit(); Loading Loading @@ -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. private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) { final LinkProperties newLp = assembleLinkProperties(); Loading @@ -1384,6 +1481,11 @@ public class IpClient extends StateMachine { 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, // wait for the provisioning completion and record the latency. mIpProvisioningMetrics.setIPv4ProvisionedLatencyOnFirstTime(newLp.isIpv4Provisioned()); Loading Loading @@ -1662,6 +1764,7 @@ public class IpClient extends StateMachine { public void enter() { stopAllIP(); mHasDisabledIPv6OnProvLoss = false; mGratuitousNaTargetAddresses.clear(); mLinkObserver.clearInterfaceParams(); resetLinkProperties(); Loading
src/android/net/util/NetworkStackUtils.java +25 −0 Original line number Diff line number Diff line Loading @@ -17,12 +17,16 @@ package android.net.util; import android.content.Context; import android.net.MacAddress; import androidx.annotation.NonNull; import com.android.net.module.util.DeviceConfigUtils; import java.io.FileDescriptor; import java.io.IOException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.SocketException; /** Loading Loading @@ -232,6 +236,12 @@ public class NetworkStackUtils { */ 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 { System.loadLibrary("networkstackutilsjni"); } Loading @@ -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. */ Loading
tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java +79 −1 Original line number Diff line number Diff line Loading @@ -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_HEADER_LEN; 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.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_ROUTERS_MULTICAST; import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN; import static com.android.net.module.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET; import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS; import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK; import static junit.framework.Assert.fail; Loading Loading @@ -141,6 +145,7 @@ import com.android.networkstack.apishim.ConstantsShim; 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.NetworkObserver; import com.android.server.NetworkObserverRegistry; import com.android.server.NetworkStackService.NetworkStackServiceManager; Loading Loading @@ -390,6 +395,7 @@ public abstract class IpClientIntegrationTestCommon { return mDhcpClient; } @Override public boolean isFeatureEnabled(final Context context, final String name, final boolean defaultEnabled) { return IpClientIntegrationTestCommon.this.isFeatureEnabled(name, defaultEnabled); Loading Loading @@ -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, final Inet4Address clientAddress, final Integer leaseTimeSec, final short mtu, final String captivePortalUrl, final Integer ipv6OnlyWaitTime) { Loading Loading @@ -1418,6 +1432,24 @@ public abstract class IpClientIntegrationTestCommon { == (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 { assertNotNull("No router solicitation received on interface within timeout", mPacketReader.popPacket(PACKET_TIMEOUT_MS, this::isRouterSolicitation)); Loading Loading @@ -1448,7 +1480,7 @@ public abstract class IpClientIntegrationTestCommon { private static ByteBuffer buildPioOption(int valid, int preferred, String prefixString) throws Exception { 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 { Loading Loading @@ -2744,4 +2776,50 @@ public abstract class IpClientIntegrationTestCommon { assertArrayEquals(packet.mUserClass, TEST_OEM_USER_CLASS_INFO); 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 } }