summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLorenzo Colitti <lorenzo@google.com>2020-04-22 16:03:41 +0900
committerLorenzo Colitti <lorenzo@google.com>2020-04-23 14:19:44 +0900
commitdc5aabd610036cf3fd2316dd0c66c655b82c407c (patch)
treebe0dc9c4fc7a85c4cf46c73ee9d1d7e2dffc7052
parent039d7960521483346382ca244d501fa136935d9e (diff)
Ensure that the NAT64 prefix is removed when its lifetime expires.
Bug: 153694684 Test: new test coverage in IpClientIntegrationTest Change-Id: Ie207940d79abbc0d92dd15becee867e72f171786
-rw-r--r--src/android/net/ip/IpClient.java3
-rw-r--r--src/android/net/ip/IpClientLinkObserver.java38
-rw-r--r--tests/integration/src/android/net/ip/IpClientIntegrationTest.java63
3 files changed, 93 insertions, 11 deletions
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 3fb5001..d736bcd 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -582,9 +582,10 @@ public class IpClient extends StateMachine {
mMinRdnssLifetimeSec);
mLinkObserver = new IpClientLinkObserver(
+ mContext, getHandler(),
mInterfaceName,
() -> sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED),
- config, getHandler(), mLog) {
+ config, mLog) {
@Override
public void onInterfaceAdded(String iface) {
super.onInterfaceAdded(iface);
diff --git a/src/android/net/ip/IpClientLinkObserver.java b/src/android/net/ip/IpClientLinkObserver.java
index bbd2176..66a4038 100644
--- a/src/android/net/ip/IpClientLinkObserver.java
+++ b/src/android/net/ip/IpClientLinkObserver.java
@@ -20,6 +20,8 @@ import static android.system.OsConstants.AF_INET6;
import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import android.app.AlarmManager;
+import android.content.Context;
import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.LinkAddress;
@@ -104,14 +106,15 @@ public class IpClientLinkObserver implements NetworkObserver {
private final Callback mCallback;
private final LinkProperties mLinkProperties;
private DnsServerRepository mDnsServerRepository;
+ private final AlarmManager mAlarmManager;
private final Configuration mConfig;
private final MyNetlinkMonitor mNetlinkMonitor;
private static final boolean DBG = false;
- public IpClientLinkObserver(String iface, Callback callback, Configuration config,
- Handler h, SharedLog log) {
+ public IpClientLinkObserver(Context context, Handler h, String iface, Callback callback,
+ Configuration config, SharedLog log) {
mInterfaceName = iface;
mTag = "NetlinkTracker/" + mInterfaceName;
mCallback = callback;
@@ -119,6 +122,7 @@ public class IpClientLinkObserver implements NetworkObserver {
mLinkProperties.setInterfaceName(mInterfaceName);
mConfig = config;
mDnsServerRepository = new DnsServerRepository(config.minRdnssLifetime);
+ mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mNetlinkMonitor = new MyNetlinkMonitor(h, log, mTag);
h.post(mNetlinkMonitor::start);
}
@@ -253,8 +257,11 @@ public class IpClientLinkObserver implements NetworkObserver {
* All methods except the constructor must be called on the handler thread.
*/
private class MyNetlinkMonitor extends NetlinkMonitor {
+ private final Handler mHandler;
+
MyNetlinkMonitor(Handler h, SharedLog log, String tag) {
super(h, log, tag, OsConstants.NETLINK_ROUTE, NetlinkConstants.RTMGRP_ND_USEROPT);
+ mHandler = h;
}
private final NetworkInformationShim mShim = NetworkInformationShimImpl.newInstance();
@@ -274,6 +281,23 @@ public class IpClientLinkObserver implements NetworkObserver {
mIfindex = ifindex;
}
+ private final AlarmManager.OnAlarmListener mExpirePref64Alarm = () -> {
+ updatePref64(mShim.getNat64Prefix(mLinkProperties),
+ mNat64PrefixExpiry, mNat64PrefixExpiry);
+ };
+
+ private void cancelPref64Alarm() {
+ mAlarmManager.cancel(mExpirePref64Alarm);
+ }
+
+ private void schedulePref64Alarm() {
+ // There is no need to cancel any existing alarms, because we are using the same
+ // OnAlarmListener object, and each such listener can only have at most one alarm.
+ final String tag = mTag + ".PREF64";
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, mNat64PrefixExpiry, tag,
+ mExpirePref64Alarm, mHandler);
+ }
+
/**
* Processes a PREF64 ND option.
*
@@ -290,10 +314,16 @@ public class IpClientLinkObserver implements NetworkObserver {
// If the prefix matches the current prefix, refresh its lifetime.
if (prefix.equals(currentPrefix)) {
mNat64PrefixExpiry = expiry;
+ if (expiry > now) {
+ schedulePref64Alarm();
+ }
}
// 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.
+ // TODO: this means that if we receive an RA that adds a new prefix and deletes the old
+ // prefix, we might receive and ignore the new prefix, then delete the old prefix, and
+ // have no prefix until the next RA is received.
if (mNat64PrefixExpiry > now) return;
// The current prefix has expired. Either replace it with the new one or delete it.
@@ -301,14 +331,14 @@ public class IpClientLinkObserver implements NetworkObserver {
// If expiry > now, then prefix != currentPrefix (due to the return statement above)
mShim.setNat64Prefix(mLinkProperties, prefix);
mNat64PrefixExpiry = expiry;
+ schedulePref64Alarm();
} else {
mShim.setNat64Prefix(mLinkProperties, null);
mNat64PrefixExpiry = 0;
+ cancelPref64Alarm();
}
mCallback.update();
-
- // TODO: send a delayed message to remove the prefix when it expires.
}
private void processPref64Option(StructNdOptPref64 opt, final long now) {
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
index ac59000..6a5aff3 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
@@ -60,6 +60,9 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.longThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doAnswer;
@@ -75,6 +78,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AlarmManager;
+import android.app.AlarmManager.OnAlarmListener;
import android.app.Instrumentation;
import android.content.ContentResolver;
import android.content.Context;
@@ -116,6 +120,7 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.system.ErrnoException;
import android.system.Os;
@@ -435,6 +440,28 @@ public class IpClientIntegrationTest {
mDependencies);
}
+ private void expectAlarmCancelled(InOrder inOrder, OnAlarmListener listener) {
+ inOrder.verify(mAlarm, timeout(TEST_TIMEOUT_MS)).cancel(eq(listener));
+ }
+
+ private OnAlarmListener expectAlarmSet(InOrder inOrder, String tagMatch, int afterSeconds) {
+ // Allow +/- 3 seconds to prevent flaky tests.
+ final long when = SystemClock.elapsedRealtime() + afterSeconds * 1000;
+ final long min = when - 3 * 1000;
+ final long max = when + 3 * 1000;
+ ArgumentCaptor<OnAlarmListener> captor = ArgumentCaptor.forClass(OnAlarmListener.class);
+ if (inOrder != null) {
+ inOrder.verify(mAlarm, timeout(TEST_TIMEOUT_MS)).setExact(
+ anyInt(), longThat(x -> x >= min && x <= max),
+ contains(tagMatch), captor.capture(), eq(mIpc.getHandler()));
+ } else {
+ verify(mAlarm, timeout(TEST_TIMEOUT_MS)).setExact(
+ anyInt(), longThat(x -> x >= min && x <= max),
+ contains(tagMatch), captor.capture(), eq(mIpc.getHandler()));
+ }
+ return captor.getValue();
+ }
+
private boolean packetContainsExpectedField(final byte[] packet, final int offset,
final byte[] expected) {
if (packet.length < offset + expected.length) return false;
@@ -1352,8 +1379,7 @@ public class IpClientIntegrationTest {
}
private void expectNoNat64PrefixUpdate(InOrder inOrder, IpPrefix unchanged) throws Exception {
- HandlerUtilsKt.waitForIdle(mIpc.getHandler(), TEST_TIMEOUT_MS);
- inOrder.verify(mCb, never()).onLinkPropertiesChange(argThat(
+ inOrder.verify(mCb, timeout(TEST_TIMEOUT_MS).times(0)).onLinkPropertiesChange(argThat(
lp -> !Objects.equals(unchanged, lp.getNat64Prefix())));
}
@@ -1380,11 +1406,16 @@ public class IpClientIntegrationTest {
waitForRouterSolicitation();
mPacketReader.sendResponse(ra);
- InOrder inOrder = inOrder(mCb);
+ // The NAT64 prefix might be detected before or after provisioning success.
+ // Don't test order between these two events.
ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
- inOrder.verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture());
+ verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture());
+ expectAlarmSet(null /*inOrder*/, "PREF64", 600);
+ reset(mCb, mAlarm);
+
+ // From now on expect events in order.
+ InOrder inOrder = inOrder(mCb, mAlarm);
- // The NAT64 prefix might have been detected before or after provisioning success.
LinkProperties lp = captor.getValue();
if (lp.getNat64Prefix() != null) {
assertEquals(prefix, lp.getNat64Prefix());
@@ -1396,30 +1427,50 @@ public class IpClientIntegrationTest {
pref64 = new StructNdOptPref64(prefix, 1800).toByteBuffer();
ra = buildRaPacket(pio, rdnss, pref64);
mPacketReader.sendResponse(ra);
+ OnAlarmListener pref64Alarm = expectAlarmSet(inOrder, "PREF64", 1800);
expectNoNat64PrefixUpdate(inOrder, prefix);
+ reset(mCb, mAlarm);
// 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);
+ expectAlarmCancelled(inOrder, pref64Alarm);
expectNat64PrefixUpdate(inOrder, null);
+ reset(mCb, mAlarm);
// Re-announce the prefix.
pref64 = new StructNdOptPref64(prefix, 600).toByteBuffer();
ra = buildRaPacket(pio, rdnss, pref64);
mPacketReader.sendResponse(ra);
+ expectAlarmSet(inOrder, "PREF64", 600);
expectNat64PrefixUpdate(inOrder, prefix);
+ reset(mCb, mAlarm);
// 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();
+ ByteBuffer otherPref64 = new StructNdOptPref64(otherPrefix, 1200).toByteBuffer();
ra = buildRaPacket(pio, rdnss, pref64, otherPref64);
mPacketReader.sendResponse(ra);
+ expectAlarmSet(inOrder, "PREF64", 600);
+ expectNoNat64PrefixUpdate(inOrder, prefix);
+ reset(mCb, mAlarm);
+ // Withdraw the prefix and expect to switch to the new prefix.
pref64 = new StructNdOptPref64(prefix, 0).toByteBuffer();
ra = buildRaPacket(pio, rdnss, pref64, otherPref64);
mPacketReader.sendResponse(ra);
+ expectAlarmCancelled(inOrder, pref64Alarm);
+ // Need a different OnAlarmListener local variable because posting it to the handler in the
+ // lambda below requires it to be final.
+ final OnAlarmListener lastAlarm = expectAlarmSet(inOrder, "PREF64", 1200);
expectNat64PrefixUpdate(inOrder, otherPrefix);
+ reset(mCb, mAlarm);
+
+ // Simulate prefix expiry.
+ mIpc.getHandler().post(() -> lastAlarm.onAlarm());
+ expectAlarmCancelled(inOrder, pref64Alarm);
+ expectNat64PrefixUpdate(inOrder, null);
}
@Test