1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net.ip;
18 
19 import static android.system.OsConstants.AF_INET6;
20 
21 import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
22 
23 import android.app.AlarmManager;
24 import android.content.Context;
25 import android.net.InetAddresses;
26 import android.net.IpPrefix;
27 import android.net.LinkAddress;
28 import android.net.LinkProperties;
29 import android.net.RouteInfo;
30 import android.net.netlink.NduseroptMessage;
31 import android.net.netlink.NetlinkConstants;
32 import android.net.netlink.NetlinkMessage;
33 import android.net.netlink.StructNdOptPref64;
34 import android.net.util.InterfaceParams;
35 import android.net.util.SharedLog;
36 import android.os.Handler;
37 import android.system.OsConstants;
38 import android.util.Log;
39 
40 import com.android.networkstack.apishim.NetworkInformationShimImpl;
41 import com.android.networkstack.apishim.common.NetworkInformationShim;
42 import com.android.server.NetworkObserver;
43 
44 import java.net.InetAddress;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.Collections;
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.Set;
51 import java.util.concurrent.TimeUnit;
52 
53 /**
54  * Keeps track of link configuration received from Netd.
55  *
56  * An instance of this class is constructed by passing in an interface name and a callback. The
57  * owner is then responsible for registering the tracker with NetworkObserverRegistry. When the
58  * class receives update notifications, it applies the update to its local LinkProperties, and if
59  * something has changed, notifies its owner of the update via the callback.
60  *
61  * The owner can then call {@code getLinkProperties()} in order to find out
62  * what changed. If in the meantime the LinkProperties stored here have changed,
63  * this class will return the current LinkProperties. Because each change
64  * triggers an update callback after the change is made, the owner may get more
65  * callbacks than strictly necessary (some of which may be no-ops), but will not
66  * be out of sync once all callbacks have been processed.
67  *
68  * Threading model:
69  *
70  * - The owner of this class is expected to create it, register it, and call
71  *   getLinkProperties or clearLinkProperties on its thread.
72  * - Most of the methods in the class are implementing NetworkObserver and are called
73  *   on the handler used to register the observer.
74  * - All accesses to mLinkProperties must be synchronized(this). All the other
75  *   member variables are immutable once the object is constructed.
76  *
77  * TODO: Now that all the methods are called on the handler thread, remove synchronization and
78  *       pass the LinkProperties to the update() callback.
79  * TODO: Stop extending NetworkObserver and get events from netlink directly.
80  *
81  * @hide
82  */
83 public class IpClientLinkObserver implements NetworkObserver {
84     private final String mTag;
85 
86     /**
87      * Callback used by {@link IpClientLinkObserver} to send update notifications.
88      */
89     public interface Callback {
90         /**
91          * Called when some properties of the link were updated.
92          *
93          * @param linkState Whether the interface link state is up as per the latest
94          *                  {@link #onInterfaceLinkStateChanged(String, boolean)} callback. This
95          *                  should only be used for metrics purposes, as it could be inconsistent
96          *                  with {@link #getLinkProperties()} in particular.
97          */
update(boolean linkState)98         void update(boolean linkState);
99     }
100 
101     /** Configuration parameters for IpClientLinkObserver. */
102     public static class Configuration {
103         public final int minRdnssLifetime;
104 
Configuration(int minRdnssLifetime)105         public Configuration(int minRdnssLifetime) {
106             this.minRdnssLifetime = minRdnssLifetime;
107         }
108     }
109 
110     private final String mInterfaceName;
111     private final Callback mCallback;
112     private final LinkProperties mLinkProperties;
113     private boolean mInterfaceLinkState;
114     private DnsServerRepository mDnsServerRepository;
115     private final AlarmManager mAlarmManager;
116     private final Configuration mConfig;
117     private final Handler mHandler;
118 
119     private final MyNetlinkMonitor mNetlinkMonitor;
120 
121     private static final boolean DBG = false;
122 
IpClientLinkObserver(Context context, Handler h, String iface, Callback callback, Configuration config, SharedLog log)123     public IpClientLinkObserver(Context context, Handler h, String iface, Callback callback,
124             Configuration config, SharedLog log) {
125         mInterfaceName = iface;
126         mTag = "NetlinkTracker/" + mInterfaceName;
127         mCallback = callback;
128         mLinkProperties = new LinkProperties();
129         mLinkProperties.setInterfaceName(mInterfaceName);
130         mConfig = config;
131         mHandler = h;
132         mInterfaceLinkState = true; // Assume up by default
133         mDnsServerRepository = new DnsServerRepository(config.minRdnssLifetime);
134         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
135         mNetlinkMonitor = new MyNetlinkMonitor(h, log, mTag);
136         mHandler.post(mNetlinkMonitor::start);
137     }
138 
shutdown()139     public void shutdown() {
140         mHandler.post(mNetlinkMonitor::stop);
141     }
142 
maybeLog(String operation, String iface, LinkAddress address)143     private void maybeLog(String operation, String iface, LinkAddress address) {
144         if (DBG) {
145             Log.d(mTag, operation + ": " + address + " on " + iface
146                     + " flags " + address.getFlags() + " scope " + address.getScope());
147         }
148     }
149 
maybeLog(String operation, Object o)150     private void maybeLog(String operation, Object o) {
151         if (DBG) {
152             Log.d(mTag, operation + ": " + o.toString());
153         }
154     }
155 
156     @Override
onInterfaceRemoved(String iface)157     public void onInterfaceRemoved(String iface) {
158         maybeLog("interfaceRemoved", iface);
159         if (mInterfaceName.equals(iface)) {
160             // Our interface was removed. Clear our LinkProperties and tell our owner that they are
161             // now empty. Note that from the moment that the interface is removed, any further
162             // interface-specific messages (e.g., RTM_DELADDR) will not reach us, because the netd
163             // code that parses them will not be able to resolve the ifindex to an interface name.
164             clearLinkProperties();
165             mCallback.update(getInterfaceLinkState());
166         }
167     }
168 
169     @Override
onInterfaceLinkStateChanged(String iface, boolean state)170     public void onInterfaceLinkStateChanged(String iface, boolean state) {
171         if (mInterfaceName.equals(iface)) {
172             maybeLog("interfaceLinkStateChanged", iface + (state ? " up" : " down"));
173             setInterfaceLinkState(state);
174         }
175     }
176 
177     @Override
onInterfaceAddressUpdated(LinkAddress address, String iface)178     public void onInterfaceAddressUpdated(LinkAddress address, String iface) {
179         if (mInterfaceName.equals(iface)) {
180             maybeLog("addressUpdated", iface, address);
181             boolean changed;
182             synchronized (this) {
183                 changed = mLinkProperties.addLinkAddress(address);
184             }
185             if (changed) {
186                 mCallback.update(getInterfaceLinkState());
187             }
188         }
189     }
190 
191     @Override
onInterfaceAddressRemoved(LinkAddress address, String iface)192     public void onInterfaceAddressRemoved(LinkAddress address, String iface) {
193         if (mInterfaceName.equals(iface)) {
194             maybeLog("addressRemoved", iface, address);
195             boolean changed;
196             synchronized (this) {
197                 changed = mLinkProperties.removeLinkAddress(address);
198             }
199             if (changed) {
200                 mCallback.update(getInterfaceLinkState());
201             }
202         }
203     }
204 
205     @Override
onRouteUpdated(RouteInfo route)206     public void onRouteUpdated(RouteInfo route) {
207         if (mInterfaceName.equals(route.getInterface())) {
208             maybeLog("routeUpdated", route);
209             boolean changed;
210             synchronized (this) {
211                 changed = mLinkProperties.addRoute(route);
212             }
213             if (changed) {
214                 mCallback.update(getInterfaceLinkState());
215             }
216         }
217     }
218 
219     @Override
onRouteRemoved(RouteInfo route)220     public void onRouteRemoved(RouteInfo route) {
221         if (mInterfaceName.equals(route.getInterface())) {
222             maybeLog("routeRemoved", route);
223             boolean changed;
224             synchronized (this) {
225                 changed = mLinkProperties.removeRoute(route);
226             }
227             if (changed) {
228                 mCallback.update(getInterfaceLinkState());
229             }
230         }
231     }
232 
233     @Override
onInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses)234     public void onInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
235         if (mInterfaceName.equals(iface)) {
236             maybeLog("interfaceDnsServerInfo", Arrays.toString(addresses));
237             boolean changed = mDnsServerRepository.addServers(lifetime, addresses);
238             if (changed) {
239                 synchronized (this) {
240                     mDnsServerRepository.setDnsServersOn(mLinkProperties);
241                 }
242                 mCallback.update(getInterfaceLinkState());
243             }
244         }
245     }
246 
247     /**
248      * Returns a copy of this object's LinkProperties.
249      */
getLinkProperties()250     public synchronized LinkProperties getLinkProperties() {
251         return new LinkProperties(mLinkProperties);
252     }
253 
254     /**
255      * Reset this object's LinkProperties.
256      */
clearLinkProperties()257     public synchronized void clearLinkProperties() {
258         // Clear the repository before clearing mLinkProperties. That way, if a clear() happens
259         // while interfaceDnsServerInfo() is being called, we'll end up with no DNS servers in
260         // mLinkProperties, as desired.
261         mDnsServerRepository = new DnsServerRepository(mConfig.minRdnssLifetime);
262         mNetlinkMonitor.clearAlarms();
263         mLinkProperties.clear();
264         mLinkProperties.setInterfaceName(mInterfaceName);
265     }
266 
getInterfaceLinkState()267     private synchronized boolean getInterfaceLinkState() {
268         return mInterfaceLinkState;
269     }
270 
setInterfaceLinkState(boolean state)271     private synchronized void setInterfaceLinkState(boolean state) {
272         mInterfaceLinkState = state;
273     }
274 
275     /** Notifies this object of new interface parameters. */
setInterfaceParams(InterfaceParams params)276     public void setInterfaceParams(InterfaceParams params) {
277         mNetlinkMonitor.setIfindex(params.index);
278     }
279 
280     /** Notifies this object not to listen on any interface. */
clearInterfaceParams()281     public void clearInterfaceParams() {
282         mNetlinkMonitor.setIfindex(0);  // 0 is never a valid ifindex.
283     }
284 
285     /**
286      * Simple NetlinkMonitor. Currently only listens for PREF64 events.
287      * All methods except the constructor must be called on the handler thread.
288      */
289     private class MyNetlinkMonitor extends NetlinkMonitor {
290         private final Handler mHandler;
291 
MyNetlinkMonitor(Handler h, SharedLog log, String tag)292         MyNetlinkMonitor(Handler h, SharedLog log, String tag) {
293             super(h, log, tag, OsConstants.NETLINK_ROUTE, NetlinkConstants.RTMGRP_ND_USEROPT);
294             mHandler = h;
295         }
296 
297         private final NetworkInformationShim mShim = NetworkInformationShimImpl.newInstance();
298 
299         private long mNat64PrefixExpiry;
300 
301         /**
302          * Current interface index. Most of this class (and of IpClient), only uses interface names,
303          * not interface indices. This means that the interface index can in theory change, and that
304          * it's not necessarily correct to get the interface name at object creation time (and in
305          * fact, when the object is created, the interface might not even exist).
306          * TODO: once all netlink events pass through this class, stop depending on interface names.
307          */
308         private int mIfindex;
309 
setIfindex(int ifindex)310         void setIfindex(int ifindex) {
311             mIfindex = ifindex;
312         }
313 
clearAlarms()314         void clearAlarms() {
315             cancelPref64Alarm();
316         }
317 
318         private final AlarmManager.OnAlarmListener mExpirePref64Alarm = () -> {
319             // Ignore the alarm if cancelPref64Alarm has already been called.
320             //
321             // TODO: in the rare case where the alarm fires and posts the lambda to the handler
322             // thread while we are processing an RA that changes the lifetime of the same prefix,
323             // this code will run anyway even if the alarm is rescheduled or cancelled. If the
324             // lifetime in the RA is zero this code will correctly do nothing, but if the lifetime
325             // is nonzero then the prefix will be added and immediately removed by this code.
326             if (mNat64PrefixExpiry == 0) return;
327             updatePref64(mShim.getNat64Prefix(mLinkProperties),
328                     mNat64PrefixExpiry, mNat64PrefixExpiry);
329         };
330 
cancelPref64Alarm()331         private void cancelPref64Alarm() {
332             // Clear the expiry in case the alarm just fired and has not been processed yet.
333             if (mNat64PrefixExpiry == 0) return;
334             mNat64PrefixExpiry = 0;
335             mAlarmManager.cancel(mExpirePref64Alarm);
336         }
337 
schedulePref64Alarm()338         private void schedulePref64Alarm() {
339             // There is no need to cancel any existing alarms, because we are using the same
340             // OnAlarmListener object, and each such listener can only have at most one alarm.
341             final String tag = mTag + ".PREF64";
342             mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, mNat64PrefixExpiry, tag,
343                     mExpirePref64Alarm, mHandler);
344         }
345 
346         /**
347          * Processes a PREF64 ND option.
348          *
349          * @param prefix The NAT64 prefix.
350          * @param now The time (as determined by SystemClock.elapsedRealtime) when the event
351          *            that triggered this method was received.
352          * @param expiry The time (as determined by SystemClock.elapsedRealtime) when the option
353          *               expires.
354          */
updatePref64(IpPrefix prefix, final long now, final long expiry)355         private synchronized void updatePref64(IpPrefix prefix, final long now,
356                 final long expiry) {
357             final IpPrefix currentPrefix = mShim.getNat64Prefix(mLinkProperties);
358 
359             // If the prefix matches the current prefix, refresh its lifetime.
360             if (prefix.equals(currentPrefix)) {
361                 mNat64PrefixExpiry = expiry;
362                 if (expiry > now) {
363                     schedulePref64Alarm();
364                 }
365             }
366 
367             // If we already have a prefix, continue using it and ignore the new one. Stopping and
368             // restarting clatd is disruptive because it will break existing IPv4 connections.
369             // Note: this means that if we receive an RA that adds a new prefix and deletes the old
370             // prefix, we might receive and ignore the new prefix, then delete the old prefix, and
371             // have no prefix until the next RA is received. This is because the kernel returns ND
372             // user options one at a time even if they are in the same RA.
373             // TODO: keep track of the last few prefixes seen, like DnsServerRepository does.
374             if (mNat64PrefixExpiry > now) return;
375 
376             // The current prefix has expired. Either replace it with the new one or delete it.
377             if (expiry > now) {
378                 // If expiry > now, then prefix != currentPrefix (due to the return statement above)
379                 mShim.setNat64Prefix(mLinkProperties, prefix);
380                 mNat64PrefixExpiry = expiry;
381                 schedulePref64Alarm();
382             } else {
383                 mShim.setNat64Prefix(mLinkProperties, null);
384                 cancelPref64Alarm();
385             }
386 
387             mCallback.update(getInterfaceLinkState());
388         }
389 
processPref64Option(StructNdOptPref64 opt, final long now)390         private void processPref64Option(StructNdOptPref64 opt, final long now) {
391             final long expiry = now + TimeUnit.SECONDS.toMillis(opt.lifetime);
392             updatePref64(opt.prefix, now, expiry);
393         }
394 
processNduseroptMessage(NduseroptMessage msg, final long whenMs)395         private void processNduseroptMessage(NduseroptMessage msg, final long whenMs) {
396             if (msg.family != AF_INET6 || msg.option == null || msg.ifindex != mIfindex) return;
397             if (msg.icmp_type != (byte) ICMPV6_ROUTER_ADVERTISEMENT) return;
398 
399             switch (msg.option.type) {
400                 case StructNdOptPref64.TYPE:
401                     processPref64Option((StructNdOptPref64) msg.option, whenMs);
402                     break;
403 
404                 default:
405                     // TODO: implement RDNSS and DNSSL.
406                     break;
407             }
408         }
409 
410         @Override
processNetlinkMessage(NetlinkMessage nlMsg, long whenMs)411         protected void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) {
412             if (!(nlMsg instanceof NduseroptMessage)) return;
413             processNduseroptMessage((NduseroptMessage) nlMsg, whenMs);
414         }
415     }
416 
417     /**
418      * Tracks DNS server updates received from Netlink.
419      *
420      * The network may announce an arbitrary number of DNS servers in Router Advertisements at any
421      * time. Each announcement has a lifetime; when the lifetime expires, the servers should not be
422      * used any more. In this way, the network can gracefully migrate clients from one set of DNS
423      * servers to another. Announcements can both raise and lower the lifetime, and an announcement
424      * can expire servers by announcing them with a lifetime of zero.
425      *
426      * Typically the system will only use a small number (2 or 3; {@code NUM_CURRENT_SERVERS}) of
427      * DNS servers at any given time. These are referred to as the current servers. In case all the
428      * current servers expire, the class also keeps track of a larger (but limited) number of
429      * servers that are promoted to current servers when the current ones expire. In order to
430      * minimize updates to the rest of the system (and potentially expensive cache flushes) this
431      * class attempts to keep the list of current servers constant where possible. More
432      * specifically, the list of current servers is only updated if a new server is learned and
433      * there are not yet {@code NUM_CURRENT_SERVERS} current servers, or if one or more of the
434      * current servers expires or is pushed out of the set. Therefore, the current servers will not
435      * necessarily be the ones with the highest lifetime, but the ones learned first.
436      *
437      * This is by design: if instead the class always preferred the servers with the highest
438      * lifetime, a (misconfigured?) network where two or more routers announce more than
439      * {@code NUM_CURRENT_SERVERS} unique servers would cause persistent oscillations.
440      *
441      * TODO: Currently servers are only expired when a new DNS update is received.
442      * Update them using timers, or possibly on every notification received by NetlinkTracker.
443      *
444      * Threading model: run by NetlinkTracker. Methods are synchronized(this) just in case netlink
445      * notifications are sent by multiple threads. If future threads use alarms to expire, those
446      * alarms must also be synchronized(this).
447      *
448      */
449     private static class DnsServerRepository {
450 
451         /** How many DNS servers we will use. 3 is suggested by RFC 6106. */
452         static final int NUM_CURRENT_SERVERS = 3;
453 
454         /** How many DNS servers we'll keep track of, in total. */
455         static final int NUM_SERVERS = 12;
456 
457         /** Stores up to {@code NUM_CURRENT_SERVERS} DNS servers we're currently using. */
458         private Set<InetAddress> mCurrentServers;
459 
460         public static final String TAG = "DnsServerRepository";
461 
462         /**
463          * Stores all the DNS servers we know about, for use when the current servers expire.
464          * Always sorted in order of decreasing expiry. The elements in this list are also the
465          * values of mIndex, and may be elements in mCurrentServers.
466          */
467         private ArrayList<DnsServerEntry> mAllServers;
468 
469         /**
470          * Indexes the servers so we can update their lifetimes more quickly in the common case
471          * where servers are not being added, but only being refreshed.
472          */
473         private HashMap<InetAddress, DnsServerEntry> mIndex;
474 
475         /**
476          * Minimum (non-zero) RDNSS lifetime to accept.
477          */
478         private final int mMinLifetime;
479 
DnsServerRepository(int minLifetime)480         DnsServerRepository(int minLifetime) {
481             mCurrentServers = new HashSet<>();
482             mAllServers = new ArrayList<>(NUM_SERVERS);
483             mIndex = new HashMap<>(NUM_SERVERS);
484             mMinLifetime = minLifetime;
485         }
486 
487         /** Sets the DNS servers of the provided LinkProperties object to the current servers. */
setDnsServersOn(LinkProperties lp)488         public synchronized void setDnsServersOn(LinkProperties lp) {
489             lp.setDnsServers(mCurrentServers);
490         }
491 
492         /**
493          * Notifies the class of new DNS server information.
494          * @param lifetime the time in seconds that the DNS servers are valid.
495          * @param addresses the string representations of the IP addresses of DNS servers to use.
496          */
addServers(long lifetime, String[] addresses)497         public synchronized boolean addServers(long lifetime, String[] addresses) {
498             // If the servers are below the minimum lifetime, don't change anything.
499             if (lifetime != 0 && lifetime < mMinLifetime) return false;
500 
501             // The lifetime is actually an unsigned 32-bit number, but Java doesn't have unsigned.
502             // Technically 0xffffffff (the maximum) is special and means "forever", but 2^32 seconds
503             // (136 years) is close enough.
504             long now = System.currentTimeMillis();
505             long expiry = now + 1000 * lifetime;
506 
507             // Go through the list of servers. For each one, update the entry if one exists, and
508             // create one if it doesn't.
509             for (String addressString : addresses) {
510                 InetAddress address;
511                 try {
512                     address = InetAddresses.parseNumericAddress(addressString);
513                 } catch (IllegalArgumentException ex) {
514                     continue;
515                 }
516 
517                 if (!updateExistingEntry(address, expiry)) {
518                     // There was no entry for this server. Create one, unless it's already expired
519                     // (i.e., if the lifetime is zero; it cannot be < 0 because it's unsigned).
520                     if (expiry > now) {
521                         DnsServerEntry entry = new DnsServerEntry(address, expiry);
522                         mAllServers.add(entry);
523                         mIndex.put(address, entry);
524                     }
525                 }
526             }
527 
528             // Sort the servers by expiry.
529             Collections.sort(mAllServers);
530 
531             // Prune excess entries and update the current server list.
532             return updateCurrentServers();
533         }
534 
updateExistingEntry(InetAddress address, long expiry)535         private synchronized boolean updateExistingEntry(InetAddress address, long expiry) {
536             DnsServerEntry existing = mIndex.get(address);
537             if (existing != null) {
538                 existing.expiry = expiry;
539                 return true;
540             }
541             return false;
542         }
543 
updateCurrentServers()544         private synchronized boolean updateCurrentServers() {
545             long now = System.currentTimeMillis();
546             boolean changed = false;
547 
548             // Prune excess or expired entries.
549             for (int i = mAllServers.size() - 1; i >= 0; i--) {
550                 if (i >= NUM_SERVERS || mAllServers.get(i).expiry <= now) {
551                     DnsServerEntry removed = mAllServers.remove(i);
552                     mIndex.remove(removed.address);
553                     changed |= mCurrentServers.remove(removed.address);
554                 } else {
555                     break;
556                 }
557             }
558 
559             // Add servers to the current set, in order of decreasing lifetime, until it has enough.
560             // Prefer existing servers over new servers in order to minimize updates to the rest of
561             // the system and avoid persistent oscillations.
562             for (DnsServerEntry entry : mAllServers) {
563                 if (mCurrentServers.size() < NUM_CURRENT_SERVERS) {
564                     changed |= mCurrentServers.add(entry.address);
565                 } else {
566                     break;
567                 }
568             }
569             return changed;
570         }
571     }
572 
573     /**
574      * Represents a DNS server entry with an expiry time.
575      *
576      * Implements Comparable so DNS server entries can be sorted by lifetime, longest-lived first.
577      * The ordering of entries with the same lifetime is unspecified, because given two servers with
578      * identical lifetimes, we don't care which one we use, and only comparing the lifetime is much
579      * faster than comparing the IP address as well.
580      *
581      * Note: this class has a natural ordering that is inconsistent with equals.
582      */
583     private static class DnsServerEntry implements Comparable<DnsServerEntry> {
584         /** The IP address of the DNS server. */
585         public final InetAddress address;
586         /** The time until which the DNS server may be used. A Java millisecond time as might be
587          * returned by currentTimeMillis(). */
588         public long expiry;
589 
DnsServerEntry(InetAddress address, long expiry)590         DnsServerEntry(InetAddress address, long expiry) throws IllegalArgumentException {
591             this.address = address;
592             this.expiry = expiry;
593         }
594 
compareTo(DnsServerEntry other)595         public int compareTo(DnsServerEntry other) {
596             return Long.compare(other.expiry, this.expiry);
597         }
598     }
599 }
600