summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLorenzo Colitti <lorenzo@google.com>2020-04-09 21:02:13 +0900
committerLorenzo Colitti <lorenzo@google.com>2020-04-20 19:45:28 +0900
commit3a0519aa5578d87dc62fd0645a5c58d36b6dcb6f (patch)
tree55cccafa676021993c2c03d146c3b7d0c3422a52
parent2e0f009fb49ee587861bcef61d8f9468f250a224 (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
-rw-r--r--common/netlinkclient/src/android/net/netlink/NetlinkConstants.java4
-rw-r--r--src/android/net/ip/IpClient.java6
-rw-r--r--src/android/net/ip/IpClientLinkObserver.java129
-rw-r--r--tests/integration/src/android/net/ip/IpClientIntegrationTest.java88
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 &lt;linux_src&gt;/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();