diff options
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(); |