Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 31c7a512 authored by Lorenzo Colitti's avatar Lorenzo Colitti
Browse files

Ensure that the NAT64 prefix is removed when its lifetime expires.

Bug: 153694684
Test: new test coverage in IpClientIntegrationTest
Change-Id: Ie207940d79abbc0d92dd15becee867e72f171786
Merged-In: Ie207940d79abbc0d92dd15becee867e72f171786
parent bd2f2f26
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -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);
+34 −4
Original line number Diff line number Diff line
@@ -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) {
+57 −6
Original line number Diff line number Diff line
@@ -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;
@@ -117,6 +121,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;
@@ -462,6 +467,28 @@ public class IpClientIntegrationTest {
        disableIpv6ProvisioningDelays();
    }

    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;
@@ -1396,8 +1423,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())));

    }
@@ -1425,11 +1451,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());
@@ -1441,30 +1472,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);
    }

    private void addIpAddressAndWaitForIt(final String iface) throws Exception {