Deprecate IPv6 prefixes no longer in use.

Bug: 30298058
Change-Id: I0fa9ece9b2fb07214971a91b77f5b07972d83bb6
diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
index a58d243..b47e079 100644
--- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
+++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
@@ -176,9 +176,21 @@
 
     private void updateRaParams(RaParams params) {
         if (mRaDaemon != null) {
+            HashSet<IpPrefix> deprecated = null;
+
+            if (mLastRaParams != null) {
+                deprecated = new HashSet<>();
+
+                for (IpPrefix ipp : mLastRaParams.prefixes) {
+                    if (params == null || !params.prefixes.contains(ipp)) {
+                        deprecated.add(ipp);
+                    }
+                }
+            }
+
             // Currently, we send spurious RAs (5) whenever there's any update.
             // TODO: Compare params with mLastParams to avoid spurious updates.
-            mRaDaemon.buildNewRa(params);
+            mRaDaemon.buildNewRa(params, deprecated);
         }
 
         mLastRaParams = params;
diff --git a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
index 407d315..1a9d2f2 100644
--- a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
+++ b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
@@ -45,7 +45,9 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -109,6 +111,11 @@
     private final byte[] mRA = new byte[IPV6_MIN_MTU];
     @GuardedBy("mLock")
     private int mRaLength;
+    @GuardedBy("mLock")
+    private final HashMap<IpPrefix, Integer> mDeprecatedPrefixes;
+
+    @GuardedBy("mLock")
+    private RaParams mRaParams;
 
     private volatile FileDescriptor mSocket;
     private volatile MulticastTransmitter mMulticastTransmitter;
@@ -141,45 +148,29 @@
         mIfIndex = ifindex;
         mHwAddr = hwaddr;
         mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mIfIndex), 0);
+        mDeprecatedPrefixes = new HashMap<>();
     }
 
-    public void buildNewRa(RaParams params) {
+    public void buildNewRa(RaParams params, HashSet<IpPrefix> newlyDeprecated) {
+        if (newlyDeprecated != null) {
+            synchronized (mLock) {
+                for (IpPrefix ipp : newlyDeprecated) {
+                    mDeprecatedPrefixes.put(ipp, MAX_URGENT_RTR_ADVERTISEMENTS);
+                }
+            }
+        }
+
+        // TODO: Send MAX_URGENT_RTR_ADVERTISEMENTS zero router lifetime RAs,
+        // iff. we have already sent an RA.
         if (params == null || params.prefixes.isEmpty()) {
             // No RA to be served at this time.
             clearRa();
             return;
         }
 
-        if (params.mtu < IPV6_MIN_MTU) {
-            params.mtu = IPV6_MIN_MTU;
-        }
-
-        final ByteBuffer ra = ByteBuffer.wrap(mRA);
-        ra.order(ByteOrder.BIG_ENDIAN);
-
         synchronized (mLock) {
-            try {
-                putHeader(ra, params.hasDefaultRoute);
-                putSlla(ra, mHwAddr);
-                // https://tools.ietf.org/html/rfc5175#section-4 says:
-                //
-                //     "MUST NOT be added to a Router Advertisement message
-                //      if no flags in the option are set."
-                //
-                // putExpandedFlagsOption(ra);
-                putMtu(ra, params.mtu);
-                for (IpPrefix ipp : params.prefixes) {
-                    putPio(ra, ipp);
-                }
-                if (params.dnses.size() > 0) {
-                    putRdnss(ra, params.dnses);
-                }
-                mRaLength = ra.position();
-            } catch (BufferOverflowException e) {
-                Log.e(TAG, "Could not construct new RA: " + e);
-                mRaLength = 0;
-                return;
-            }
+            mRaParams = params;
+            assembleRa();
         }
 
         maybeNotifyMulticastTransmitter();
@@ -216,6 +207,64 @@
         }
     }
 
+    private void assembleRa() {
+        final ByteBuffer ra = ByteBuffer.wrap(mRA);
+        ra.order(ByteOrder.BIG_ENDIAN);
+
+        synchronized (mLock) {
+            try {
+                putHeader(ra, mRaParams.hasDefaultRoute);
+
+                putSlla(ra, mHwAddr);
+
+                // https://tools.ietf.org/html/rfc5175#section-4 says:
+                //
+                //     "MUST NOT be added to a Router Advertisement message
+                //      if no flags in the option are set."
+                //
+                // putExpandedFlagsOption(ra);
+
+                putMtu(ra, mRaParams.mtu);
+
+                for (IpPrefix ipp : mRaParams.prefixes) {
+                    putPio(ra, ipp, DEFAULT_LIFETIME, DEFAULT_LIFETIME);
+                    mDeprecatedPrefixes.remove(ipp);
+                }
+
+                for (IpPrefix ipp : mDeprecatedPrefixes.keySet()) {
+                    putPio(ra, ipp, 0, 0);
+                }
+
+                if (mRaParams.dnses.size() > 0) {
+                    putRdnss(ra, mRaParams.dnses);
+                }
+
+                mRaLength = ra.position();
+            } catch (BufferOverflowException e) {
+                Log.e(TAG, "Could not construct new RA: " + e);
+                mRaLength = 0;
+                return;
+            }
+        }
+    }
+
+    private int decrementDeprecatedPrefixes() {
+        int removed = 0;
+
+        synchronized (mLock) {
+            for (Map.Entry<IpPrefix, Integer> kv : mDeprecatedPrefixes.entrySet()) {
+                if (kv.getValue() == 0) {
+                    mDeprecatedPrefixes.remove(kv.getKey());
+                    removed++;
+                } else {
+                    kv.setValue(kv.getValue() - 1);
+                }
+            }
+        }
+
+        return removed;
+    }
+
     private void maybeNotifyMulticastTransmitter() {
         final MulticastTransmitter m = mMulticastTransmitter;
         if (m != null) {
@@ -325,10 +374,11 @@
         ra.put(ND_OPTION_MTU)
           .put(MTU_NUM_8OCTETS)
           .putShort(asShort(0))
-          .putInt(mtu);
+          .putInt((mtu < IPV6_MIN_MTU) ? IPV6_MIN_MTU : mtu);
     }
 
-    private static void putPio(ByteBuffer ra, IpPrefix ipp) {
+    private static void putPio(ByteBuffer ra, IpPrefix ipp,
+                               int validTime, int preferredTime) {
         /**
             Prefix Information
 
@@ -359,13 +409,17 @@
         final byte ND_OPTION_PIO = 3;
         final byte PIO_NUM_8OCTETS = 4;
 
+        if (validTime < 0) validTime = 0;
+        if (preferredTime < 0) preferredTime = 0;
+        if (preferredTime > validTime) preferredTime = validTime;
+
         final byte[] addr = ipp.getAddress().getAddress();
         ra.put(ND_OPTION_PIO)
           .put(PIO_NUM_8OCTETS)
           .put(asByte(prefixLength))
-          .put(asByte(0xc0))  // L&A set
-          .putInt(DEFAULT_LIFETIME)
-          .putInt(DEFAULT_LIFETIME)
+          .put(asByte(0xc0)) /* L & A set */
+          .putInt(validTime)
+          .putInt(preferredTime)
           .putInt(0)
           .put(addr);
     }
@@ -547,6 +601,11 @@
                 }
 
                 maybeSendRA(mAllNodes);
+                if (decrementDeprecatedPrefixes() > 0) {
+                    // At least one deprecated PIO has been removed;
+                    // reassemble the RA.
+                    assembleRa();
+                }
             }
         }
 
@@ -560,15 +619,17 @@
         }
 
         private int getNextMulticastTransmitDelaySec() {
+            int countDeprecatedPrefixes = 0;
             synchronized (mLock) {
                 if (mRaLength < MIN_RA_HEADER_SIZE) {
                     // No actual RA to send; just sleep for 1 day.
                     return DAY_IN_SECONDS;
                 }
+                countDeprecatedPrefixes = mDeprecatedPrefixes.size();
             }
 
             final int urgentPending = mUrgentAnnouncements.getAndDecrement();
-            if (urgentPending > 0) {
+            if (urgentPending > 0 || countDeprecatedPrefixes > 0) {
                 return MIN_DELAY_BETWEEN_RAS_SEC;
             }