summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 <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();