diff options
author | Lorenzo Colitti <lorenzo@google.com> | 2020-04-09 21:02:13 +0900 |
---|---|---|
committer | Lorenzo Colitti <lorenzo@google.com> | 2020-04-20 19:45:28 +0900 |
commit | 3a0519aa5578d87dc62fd0645a5c58d36b6dcb6f (patch) | |
tree | 55cccafa676021993c2c03d146c3b7d0c3422a52 | |
parent | 2e0f009fb49ee587861bcef61d8f9468f250a224 (diff) |
Listen for pref64 RA attributes in IpClientLinkObserver.
This allows IpClient to parse the pref64 RA option and put it
in the LinkProperties to be sent to ConnectivityService.
IpClientLinkObserver is a natural place for this because it is
the part of IpClient that is already tasked with receiving
netlink events and storing the results in IpClient's
LinkProperties.
Instead of using the path used by most attributes, which are
parsed by NetlinkHandler, converted to a string array, then
re-parsed and sent over binder call to the networkstack, simply
open a netlink socket in the networkstack process and read the
netlink messages from there. In the future, we can build on this
to parse other netlink messages (e.g., IP addresses, routes,
RDNSS, etc.) in the networkstack and entirely remove the
dependency on netd's NetlinkHandler, which is crufty, hard to
extend, and does not support interface indices.
This means that the pref64 attribute will not be ordered with
respect to other netlink events. This is acceptable because the
pref64 attribute does not need to be ordered with any other
information and its presence or absence does not cause
provisioning to succeed or fail. Today the pref64 is learned
through an entirely different codepath (DNS lookups) and that is
not ordered in any way either.
This CL does not change the threading model: the netlink updates
are processed on the handler thread like all the other updates
seen by IpClientLinkObserver, and all access to mLinkProperties
is synchronized (this). This synchronization is no longer
necessary because everything is on the handler thread anyway,
but that will be cleaned up in a future CL.
Because netlink events contain interface indices, but IpClient
and netd deal with interface names, IpClientLinkObserver must be
told what the interface index is. This is done when
startProvisioning is called, because that is when IpClient
fetches the interface parameters including the MAC address and
interface index. It cannot be done when IpClientLinkObserver is
started, because at that time the interface might not exist, or
might exist with a previous interface index. The interface index
is cleared when IpClient enters the stopped state and the
LinkProperties are cleared.
Bug: 153694684
Test: atest NetworkStackNextIntegrationTests:IpClientIntegrationTest#testPref64Option --iterations 100
Change-Id: I3f8d2fbf2e203c6f90029947fa55b5e0b3b06d94
4 files changed, 222 insertions, 5 deletions
diff --git a/common/netlinkclient/src/android/net/netlink/NetlinkConstants.java b/common/netlinkclient/src/android/net/netlink/NetlinkConstants.java index b8fd4b1..9db09c4 100644 --- a/common/netlinkclient/src/android/net/netlink/NetlinkConstants.java +++ b/common/netlinkclient/src/android/net/netlink/NetlinkConstants.java @@ -106,6 +106,10 @@ public class NetlinkConstants { /* see <linux_src>/include/uapi/linux/sock_diag.h */ public static final short SOCK_DIAG_BY_FAMILY = 20; + // Netlink groups. + public static final int RTNLGRP_ND_USEROPT = 20; + public static final int RTMGRP_ND_USEROPT = 1 << (RTNLGRP_ND_USEROPT - 1); + public static String stringForNlMsgType(short nlm_type) { switch (nlm_type) { case NLMSG_NOOP: return "NLMSG_NOOP"; diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java index 4ddcc13..3fb5001 100644 --- a/src/android/net/ip/IpClient.java +++ b/src/android/net/ip/IpClient.java @@ -583,7 +583,8 @@ public class IpClient extends StateMachine { mLinkObserver = new IpClientLinkObserver( mInterfaceName, - () -> sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED), config) { + () -> sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED), + config, getHandler(), mLog) { @Override public void onInterfaceAdded(String iface) { super.onInterfaceAdded(iface); @@ -1225,6 +1226,7 @@ public class IpClient extends StateMachine { newLp.addRoute(route); } addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers()); + newLp.setNat64Prefix(netlinkLinkProperties.getNat64Prefix()); // [3] Add in data from DHCPv4, if available. // @@ -1563,6 +1565,7 @@ public class IpClient extends StateMachine { public void enter() { stopAllIP(); + mLinkObserver.clearInterfaceParams(); resetLinkProperties(); if (mStartTimeMillis > 0) { // Completed a life-cycle; send a final empty LinkProperties @@ -1712,6 +1715,7 @@ public class IpClient extends StateMachine { transitionTo(mStoppedState); return; } + mLinkObserver.setInterfaceParams(mInterfaceParams); mCallback.setNeighborDiscoveryOffload(true); } diff --git a/src/android/net/ip/IpClientLinkObserver.java b/src/android/net/ip/IpClientLinkObserver.java index 02bf5f0..bbd2176 100644 --- a/src/android/net/ip/IpClientLinkObserver.java +++ b/src/android/net/ip/IpClientLinkObserver.java @@ -16,12 +16,27 @@ package android.net.ip; +import static android.system.OsConstants.AF_INET6; + +import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; + import android.net.InetAddresses; +import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.RouteInfo; +import android.net.netlink.NduseroptMessage; +import android.net.netlink.NetlinkConstants; +import android.net.netlink.NetlinkMessage; +import android.net.netlink.StructNdOptPref64; +import android.net.util.InterfaceParams; +import android.net.util.SharedLog; +import android.os.Handler; +import android.system.OsConstants; import android.util.Log; +import com.android.networkstack.apishim.NetworkInformationShim; +import com.android.networkstack.apishim.NetworkInformationShimImpl; import com.android.server.NetworkObserver; import java.net.InetAddress; @@ -31,6 +46,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.TimeUnit; /** * Keeps track of link configuration received from Netd. @@ -56,6 +72,10 @@ import java.util.Set; * - All accesses to mLinkProperties must be synchronized(this). All the other * member variables are immutable once the object is constructed. * + * TODO: Now that all the methods are called on the handler thread, remove synchronization and + * pass the LinkProperties to the update() callback. + * TODO: Stop extending NetworkObserver and get events from netlink directly. + * * @hide */ public class IpClientLinkObserver implements NetworkObserver { @@ -86,16 +106,21 @@ public class IpClientLinkObserver implements NetworkObserver { private DnsServerRepository mDnsServerRepository; private final Configuration mConfig; + private final MyNetlinkMonitor mNetlinkMonitor; + private static final boolean DBG = false; - public IpClientLinkObserver(String iface, Callback callback, Configuration config) { - mTag = "NetlinkTracker/" + iface; + public IpClientLinkObserver(String iface, Callback callback, Configuration config, + Handler h, SharedLog log) { mInterfaceName = iface; + mTag = "NetlinkTracker/" + mInterfaceName; mCallback = callback; mLinkProperties = new LinkProperties(); mLinkProperties.setInterfaceName(mInterfaceName); mConfig = config; mDnsServerRepository = new DnsServerRepository(config.minRdnssLifetime); + mNetlinkMonitor = new MyNetlinkMonitor(h, log, mTag); + h.post(mNetlinkMonitor::start); } private void maybeLog(String operation, String iface, LinkAddress address) { @@ -213,6 +238,106 @@ public class IpClientLinkObserver implements NetworkObserver { mLinkProperties.setInterfaceName(mInterfaceName); } + /** Notifies this object of new interface parameters. */ + public void setInterfaceParams(InterfaceParams params) { + mNetlinkMonitor.setIfindex(params.index); + } + + /** Notifies this object not to listen on any interface. */ + public void clearInterfaceParams() { + mNetlinkMonitor.setIfindex(0); // 0 is never a valid ifindex. + } + + /** + * Simple NetlinkMonitor. Currently only listens for PREF64 events. + * All methods except the constructor must be called on the handler thread. + */ + private class MyNetlinkMonitor extends NetlinkMonitor { + MyNetlinkMonitor(Handler h, SharedLog log, String tag) { + super(h, log, tag, OsConstants.NETLINK_ROUTE, NetlinkConstants.RTMGRP_ND_USEROPT); + } + + private final NetworkInformationShim mShim = NetworkInformationShimImpl.newInstance(); + + private long mNat64PrefixExpiry; + + /** + * Current interface index. Most of this class (and of IpClient), only uses interface names, + * not interface indices. This means that the interface index can in theory change, and that + * it's not necessarily correct to get the interface name at object creation time (and in + * fact, when the object is created, the interface might not even exist). + * TODO: once all netlink events pass through this class, stop depending on interface names. + */ + private int mIfindex; + + void setIfindex(int ifindex) { + mIfindex = ifindex; + } + + /** + * Processes a PREF64 ND option. + * + * @param prefix The NAT64 prefix. + * @param now The time (as determined by SystemClock.elapsedRealtime) when the event + * that triggered this method was received. + * @param expiry The time (as determined by SystemClock.elapsedRealtime) when the option + * expires. + */ + private synchronized void updatePref64(IpPrefix prefix, final long now, + final long expiry) { + final IpPrefix currentPrefix = mShim.getNat64Prefix(mLinkProperties); + + // If the prefix matches the current prefix, refresh its lifetime. + if (prefix.equals(currentPrefix)) { + mNat64PrefixExpiry = expiry; + } + + // If we already have a prefix, continue using it and ignore the new one. Stopping and + // restarting clatd is disruptive because it will break existing IPv4 connections. + if (mNat64PrefixExpiry > now) return; + + // The current prefix has expired. Either replace it with the new one or delete it. + if (expiry > now) { + // If expiry > now, then prefix != currentPrefix (due to the return statement above) + mShim.setNat64Prefix(mLinkProperties, prefix); + mNat64PrefixExpiry = expiry; + } else { + mShim.setNat64Prefix(mLinkProperties, null); + mNat64PrefixExpiry = 0; + } + + mCallback.update(); + + // TODO: send a delayed message to remove the prefix when it expires. + } + + private void processPref64Option(StructNdOptPref64 opt, final long now) { + final long expiry = now + TimeUnit.SECONDS.toMillis(opt.lifetime); + updatePref64(opt.prefix, now, expiry); + } + + private void processNduseroptMessage(NduseroptMessage msg, final long whenMs) { + if (msg.family != AF_INET6 || msg.option == null || msg.ifindex != mIfindex) return; + if (msg.icmp_type != (byte) ICMPV6_ROUTER_ADVERTISEMENT) return; + + switch (msg.option.type) { + case StructNdOptPref64.TYPE: + processPref64Option((StructNdOptPref64) msg.option, whenMs); + break; + + default: + // TODO: implement RDNSS and DNSSL. + break; + } + } + + @Override + protected void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) { + if (!(nlMsg instanceof NduseroptMessage)) return; + processNduseroptMessage((NduseroptMessage) nlMsg, whenMs); + } + } + /** * Tracks DNS server updates received from Netlink. * diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java index fdeddca..ac59000 100644 --- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java +++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java @@ -65,6 +65,7 @@ import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; @@ -103,6 +104,7 @@ import android.net.dhcp.DhcpRequestPacket; import android.net.ipmemorystore.NetworkAttributes; import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener; import android.net.ipmemorystore.Status; +import android.net.netlink.StructNdOptPref64; import android.net.shared.ProvisioningConfiguration; import android.net.shared.ProvisioningConfiguration.ScanResultInfo; import android.net.util.InterfaceParams; @@ -138,6 +140,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; @@ -1279,12 +1282,16 @@ public class IpClientIntegrationTest { return packet; } - @Test - public void testRaRdnss() throws Exception { + private void disableRouterSolicitationDelay() throws Exception { // Speed up the test by removing router_solicitation_delay. // We don't need to restore the default value because the interface is removed in tearDown. // TODO: speed up further by not waiting for RA but keying off first IPv6 packet. mNetd.setProcSysNet(INetd.IPV6, INetd.CONF, mIfaceName, "router_solicitation_delay", "0"); + } + + @Test + public void testRaRdnss() throws Exception { + disableRouterSolicitationDelay(); ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() @@ -1338,6 +1345,83 @@ public class IpClientIntegrationTest { reset(mCb); } + private void expectNat64PrefixUpdate(InOrder inOrder, IpPrefix expected) throws Exception { + inOrder.verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange( + argThat(lp -> Objects.equals(expected, lp.getNat64Prefix()))); + + } + + private void expectNoNat64PrefixUpdate(InOrder inOrder, IpPrefix unchanged) throws Exception { + HandlerUtilsKt.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS); + inOrder.verify(mCb, never()).onLinkPropertiesChange(argThat( + lp -> !Objects.equals(unchanged, lp.getNat64Prefix()))); + + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testPref64Option() throws Exception { + disableRouterSolicitationDelay(); + + ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() + .withoutIpReachabilityMonitor() + .withoutIPv4() + .build(); + mIpc.startProvisioning(config); + + final String dnsServer = "2001:4860:4860::64"; + final IpPrefix prefix = new IpPrefix("64:ff9b::/96"); + final IpPrefix otherPrefix = new IpPrefix("2001:db8:64::/96"); + + final ByteBuffer pio = buildPioOption(600, 300, "2001:db8:1::/64"); + ByteBuffer rdnss = buildRdnssOption(600, dnsServer); + ByteBuffer pref64 = new StructNdOptPref64(prefix, 600).toByteBuffer(); + ByteBuffer ra = buildRaPacket(pio, rdnss, pref64); + + waitForRouterSolicitation(); + mPacketReader.sendResponse(ra); + + InOrder inOrder = inOrder(mCb); + ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class); + inOrder.verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture()); + + // The NAT64 prefix might have been detected before or after provisioning success. + LinkProperties lp = captor.getValue(); + if (lp.getNat64Prefix() != null) { + assertEquals(prefix, lp.getNat64Prefix()); + } else { + expectNat64PrefixUpdate(inOrder, prefix); + } + + // Increase the lifetime and expect the prefix not to change. + pref64 = new StructNdOptPref64(prefix, 1800).toByteBuffer(); + ra = buildRaPacket(pio, rdnss, pref64); + mPacketReader.sendResponse(ra); + expectNoNat64PrefixUpdate(inOrder, prefix); + + // Withdraw the prefix and expect it to be set to null. + pref64 = new StructNdOptPref64(prefix, 0).toByteBuffer(); + ra = buildRaPacket(pio, rdnss, pref64); + mPacketReader.sendResponse(ra); + expectNat64PrefixUpdate(inOrder, null); + + // Re-announce the prefix. + pref64 = new StructNdOptPref64(prefix, 600).toByteBuffer(); + ra = buildRaPacket(pio, rdnss, pref64); + mPacketReader.sendResponse(ra); + expectNat64PrefixUpdate(inOrder, prefix); + + // Announce two prefixes. Don't expect any update because if there is already a NAT64 + // prefix, any new prefix is ignored. + ByteBuffer otherPref64 = new StructNdOptPref64(otherPrefix, 600).toByteBuffer(); + ra = buildRaPacket(pio, rdnss, pref64, otherPref64); + mPacketReader.sendResponse(ra); + + pref64 = new StructNdOptPref64(prefix, 0).toByteBuffer(); + ra = buildRaPacket(pio, rdnss, pref64, otherPref64); + mPacketReader.sendResponse(ra); + expectNat64PrefixUpdate(inOrder, otherPrefix); + } + @Test public void testIpClientClearingIpAddressState() throws Exception { final long currentTime = System.currentTimeMillis(); |