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

Commit b2ff3e8c authored by Lorenzo Colitti's avatar Lorenzo Colitti Committed by Gerrit Code Review
Browse files

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

parents b2ff3ca8 31c7a512
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 {