• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.net.module.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             final boolean linkState;
165             synchronized (this) {
166                 clearLinkProperties();
167                 linkState = getInterfaceLinkStateLocked();
168             }
169             mCallback.update(linkState);
170         }
171     }
172 
173     @Override
onInterfaceLinkStateChanged(String iface, boolean state)174     public void onInterfaceLinkStateChanged(String iface, boolean state) {
175         if (mInterfaceName.equals(iface)) {
176             maybeLog("interfaceLinkStateChanged", iface + (state ? " up" : " down"));
177             synchronized (this) {
178                 setInterfaceLinkStateLocked(state);
179             }
180         }
181     }
182 
183     @Override
onInterfaceAddressUpdated(LinkAddress address, String iface)184     public void onInterfaceAddressUpdated(LinkAddress address, String iface) {
185         if (mInterfaceName.equals(iface)) {
186             maybeLog("addressUpdated", iface, address);
187             final boolean changed;
188             final boolean linkState;
189             synchronized (this) {
190                 changed = mLinkProperties.addLinkAddress(address);
191                 linkState = getInterfaceLinkStateLocked();
192             }
193             if (changed) {
194                 mCallback.update(linkState);
195             }
196         }
197     }
198 
199     @Override
onInterfaceAddressRemoved(LinkAddress address, String iface)200     public void onInterfaceAddressRemoved(LinkAddress address, String iface) {
201         if (mInterfaceName.equals(iface)) {
202             maybeLog("addressRemoved", iface, address);
203             final boolean changed;
204             final boolean linkState;
205             synchronized (this) {
206                 changed = mLinkProperties.removeLinkAddress(address);
207                 linkState = getInterfaceLinkStateLocked();
208             }
209             if (changed) {
210                 mCallback.update(linkState);
211             }
212         }
213     }
214 
215     @Override
onRouteUpdated(RouteInfo route)216     public void onRouteUpdated(RouteInfo route) {
217         if (mInterfaceName.equals(route.getInterface())) {
218             maybeLog("routeUpdated", route);
219             final boolean changed;
220             final boolean linkState;
221             synchronized (this) {
222                 changed = mLinkProperties.addRoute(route);
223                 linkState = getInterfaceLinkStateLocked();
224             }
225             if (changed) {
226                 mCallback.update(linkState);
227             }
228         }
229     }
230 
231     @Override
onRouteRemoved(RouteInfo route)232     public void onRouteRemoved(RouteInfo route) {
233         if (mInterfaceName.equals(route.getInterface())) {
234             maybeLog("routeRemoved", route);
235             final boolean changed;
236             final boolean linkState;
237             synchronized (this) {
238                 changed = mLinkProperties.removeRoute(route);
239                 linkState = getInterfaceLinkStateLocked();
240             }
241             if (changed) {
242                 mCallback.update(linkState);
243             }
244         }
245     }
246 
247     @Override
onInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses)248     public void onInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
249         if (mInterfaceName.equals(iface)) {
250             maybeLog("interfaceDnsServerInfo", Arrays.toString(addresses));
251             final boolean changed = mDnsServerRepository.addServers(lifetime, addresses);
252             final boolean linkState;
253             if (changed) {
254                 synchronized (this) {
255                     mDnsServerRepository.setDnsServersOn(mLinkProperties);
256                     linkState = getInterfaceLinkStateLocked();
257                 }
258                 mCallback.update(linkState);
259             }
260         }
261     }
262 
263     /**
264      * Returns a copy of this object's LinkProperties.
265      */
getLinkProperties()266     public synchronized LinkProperties getLinkProperties() {
267         return new LinkProperties(mLinkProperties);
268     }
269 
270     /**
271      * Reset this object's LinkProperties.
272      */
clearLinkProperties()273     public synchronized void clearLinkProperties() {
274         // Clear the repository before clearing mLinkProperties. That way, if a clear() happens
275         // while interfaceDnsServerInfo() is being called, we'll end up with no DNS servers in
276         // mLinkProperties, as desired.
277         mDnsServerRepository = new DnsServerRepository(mConfig.minRdnssLifetime);
278         mNetlinkMonitor.clearAlarms();
279         mLinkProperties.clear();
280         mLinkProperties.setInterfaceName(mInterfaceName);
281     }
282 
getInterfaceLinkStateLocked()283     private boolean getInterfaceLinkStateLocked() {
284         return mInterfaceLinkState;
285     }
286 
setInterfaceLinkStateLocked(boolean state)287     private void setInterfaceLinkStateLocked(boolean state) {
288         mInterfaceLinkState = state;
289     }
290 
291     /** Notifies this object of new interface parameters. */
setInterfaceParams(InterfaceParams params)292     public void setInterfaceParams(InterfaceParams params) {
293         mNetlinkMonitor.setIfindex(params.index);
294     }
295 
296     /** Notifies this object not to listen on any interface. */
clearInterfaceParams()297     public void clearInterfaceParams() {
298         mNetlinkMonitor.setIfindex(0);  // 0 is never a valid ifindex.
299     }
300 
301     /**
302      * Simple NetlinkMonitor. Currently only listens for PREF64 events.
303      * All methods except the constructor must be called on the handler thread.
304      */
305     private class MyNetlinkMonitor extends NetlinkMonitor {
306         private final Handler mHandler;
307 
MyNetlinkMonitor(Handler h, SharedLog log, String tag)308         MyNetlinkMonitor(Handler h, SharedLog log, String tag) {
309             super(h, log, tag, OsConstants.NETLINK_ROUTE, NetlinkConstants.RTMGRP_ND_USEROPT);
310             mHandler = h;
311         }
312 
313         private final NetworkInformationShim mShim = NetworkInformationShimImpl.newInstance();
314 
315         private long mNat64PrefixExpiry;
316 
317         /**
318          * Current interface index. Most of this class (and of IpClient), only uses interface names,
319          * not interface indices. This means that the interface index can in theory change, and that
320          * it's not necessarily correct to get the interface name at object creation time (and in
321          * fact, when the object is created, the interface might not even exist).
322          * TODO: once all netlink events pass through this class, stop depending on interface names.
323          */
324         private int mIfindex;
325 
setIfindex(int ifindex)326         void setIfindex(int ifindex) {
327             mIfindex = ifindex;
328         }
329 
clearAlarms()330         void clearAlarms() {
331             cancelPref64Alarm();
332         }
333 
334         private final AlarmManager.OnAlarmListener mExpirePref64Alarm = () -> {
335             // Ignore the alarm if cancelPref64Alarm has already been called.
336             //
337             // TODO: in the rare case where the alarm fires and posts the lambda to the handler
338             // thread while we are processing an RA that changes the lifetime of the same prefix,
339             // this code will run anyway even if the alarm is rescheduled or cancelled. If the
340             // lifetime in the RA is zero this code will correctly do nothing, but if the lifetime
341             // is nonzero then the prefix will be added and immediately removed by this code.
342             if (mNat64PrefixExpiry == 0) return;
343             updatePref64(mShim.getNat64Prefix(mLinkProperties),
344                     mNat64PrefixExpiry, mNat64PrefixExpiry);
345         };
346 
cancelPref64Alarm()347         private void cancelPref64Alarm() {
348             // Clear the expiry in case the alarm just fired and has not been processed yet.
349             if (mNat64PrefixExpiry == 0) return;
350             mNat64PrefixExpiry = 0;
351             mAlarmManager.cancel(mExpirePref64Alarm);
352         }
353 
schedulePref64Alarm()354         private void schedulePref64Alarm() {
355             // There is no need to cancel any existing alarms, because we are using the same
356             // OnAlarmListener object, and each such listener can only have at most one alarm.
357             final String tag = mTag + ".PREF64";
358             mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, mNat64PrefixExpiry, tag,
359                     mExpirePref64Alarm, mHandler);
360         }
361 
362         /**
363          * Processes a PREF64 ND option.
364          *
365          * @param prefix The NAT64 prefix.
366          * @param now The time (as determined by SystemClock.elapsedRealtime) when the event
367          *            that triggered this method was received.
368          * @param expiry The time (as determined by SystemClock.elapsedRealtime) when the option
369          *               expires.
370          */
updatePref64(IpPrefix prefix, final long now, final long expiry)371         private synchronized void updatePref64(IpPrefix prefix, final long now,
372                 final long expiry) {
373             final IpPrefix currentPrefix = mShim.getNat64Prefix(mLinkProperties);
374 
375             // If the prefix matches the current prefix, refresh its lifetime.
376             if (prefix.equals(currentPrefix)) {
377                 mNat64PrefixExpiry = expiry;
378                 if (expiry > now) {
379                     schedulePref64Alarm();
380                 }
381             }
382 
383             // If we already have a prefix, continue using it and ignore the new one. Stopping and
384             // restarting clatd is disruptive because it will break existing IPv4 connections.
385             // Note: this means that if we receive an RA that adds a new prefix and deletes the old
386             // prefix, we might receive and ignore the new prefix, then delete the old prefix, and
387             // have no prefix until the next RA is received. This is because the kernel returns ND
388             // user options one at a time even if they are in the same RA.
389             // TODO: keep track of the last few prefixes seen, like DnsServerRepository does.
390             if (mNat64PrefixExpiry > now) return;
391 
392             // The current prefix has expired. Either replace it with the new one or delete it.
393             if (expiry > now) {
394                 // If expiry > now, then prefix != currentPrefix (due to the return statement above)
395                 mShim.setNat64Prefix(mLinkProperties, prefix);
396                 mNat64PrefixExpiry = expiry;
397                 schedulePref64Alarm();
398             } else {
399                 mShim.setNat64Prefix(mLinkProperties, null);
400                 cancelPref64Alarm();
401             }
402 
403             mCallback.update(getInterfaceLinkStateLocked());
404         }
405 
processPref64Option(StructNdOptPref64 opt, final long now)406         private void processPref64Option(StructNdOptPref64 opt, final long now) {
407             final long expiry = now + TimeUnit.SECONDS.toMillis(opt.lifetime);
408             updatePref64(opt.prefix, now, expiry);
409         }
410 
processNduseroptMessage(NduseroptMessage msg, final long whenMs)411         private void processNduseroptMessage(NduseroptMessage msg, final long whenMs) {
412             if (msg.family != AF_INET6 || msg.option == null || msg.ifindex != mIfindex) return;
413             if (msg.icmp_type != (byte) ICMPV6_ROUTER_ADVERTISEMENT) return;
414 
415             switch (msg.option.type) {
416                 case StructNdOptPref64.TYPE:
417                     processPref64Option((StructNdOptPref64) msg.option, whenMs);
418                     break;
419 
420                 default:
421                     // TODO: implement RDNSS and DNSSL.
422                     break;
423             }
424         }
425 
426         @Override
processNetlinkMessage(NetlinkMessage nlMsg, long whenMs)427         protected void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) {
428             if (!(nlMsg instanceof NduseroptMessage)) return;
429             processNduseroptMessage((NduseroptMessage) nlMsg, whenMs);
430         }
431     }
432 
433     /**
434      * Tracks DNS server updates received from Netlink.
435      *
436      * The network may announce an arbitrary number of DNS servers in Router Advertisements at any
437      * time. Each announcement has a lifetime; when the lifetime expires, the servers should not be
438      * used any more. In this way, the network can gracefully migrate clients from one set of DNS
439      * servers to another. Announcements can both raise and lower the lifetime, and an announcement
440      * can expire servers by announcing them with a lifetime of zero.
441      *
442      * Typically the system will only use a small number (2 or 3; {@code NUM_CURRENT_SERVERS}) of
443      * DNS servers at any given time. These are referred to as the current servers. In case all the
444      * current servers expire, the class also keeps track of a larger (but limited) number of
445      * servers that are promoted to current servers when the current ones expire. In order to
446      * minimize updates to the rest of the system (and potentially expensive cache flushes) this
447      * class attempts to keep the list of current servers constant where possible. More
448      * specifically, the list of current servers is only updated if a new server is learned and
449      * there are not yet {@code NUM_CURRENT_SERVERS} current servers, or if one or more of the
450      * current servers expires or is pushed out of the set. Therefore, the current servers will not
451      * necessarily be the ones with the highest lifetime, but the ones learned first.
452      *
453      * This is by design: if instead the class always preferred the servers with the highest
454      * lifetime, a (misconfigured?) network where two or more routers announce more than
455      * {@code NUM_CURRENT_SERVERS} unique servers would cause persistent oscillations.
456      *
457      * TODO: Currently servers are only expired when a new DNS update is received.
458      * Update them using timers, or possibly on every notification received by NetlinkTracker.
459      *
460      * Threading model: run by NetlinkTracker. Methods are synchronized(this) just in case netlink
461      * notifications are sent by multiple threads. If future threads use alarms to expire, those
462      * alarms must also be synchronized(this).
463      *
464      */
465     private static class DnsServerRepository {
466 
467         /** How many DNS servers we will use. 3 is suggested by RFC 6106. */
468         static final int NUM_CURRENT_SERVERS = 3;
469 
470         /** How many DNS servers we'll keep track of, in total. */
471         static final int NUM_SERVERS = 12;
472 
473         /** Stores up to {@code NUM_CURRENT_SERVERS} DNS servers we're currently using. */
474         private Set<InetAddress> mCurrentServers;
475 
476         public static final String TAG = "DnsServerRepository";
477 
478         /**
479          * Stores all the DNS servers we know about, for use when the current servers expire.
480          * Always sorted in order of decreasing expiry. The elements in this list are also the
481          * values of mIndex, and may be elements in mCurrentServers.
482          */
483         private ArrayList<DnsServerEntry> mAllServers;
484 
485         /**
486          * Indexes the servers so we can update their lifetimes more quickly in the common case
487          * where servers are not being added, but only being refreshed.
488          */
489         private HashMap<InetAddress, DnsServerEntry> mIndex;
490 
491         /**
492          * Minimum (non-zero) RDNSS lifetime to accept.
493          */
494         private final int mMinLifetime;
495 
DnsServerRepository(int minLifetime)496         DnsServerRepository(int minLifetime) {
497             mCurrentServers = new HashSet<>();
498             mAllServers = new ArrayList<>(NUM_SERVERS);
499             mIndex = new HashMap<>(NUM_SERVERS);
500             mMinLifetime = minLifetime;
501         }
502 
503         /** Sets the DNS servers of the provided LinkProperties object to the current servers. */
setDnsServersOn(LinkProperties lp)504         public synchronized void setDnsServersOn(LinkProperties lp) {
505             lp.setDnsServers(mCurrentServers);
506         }
507 
508         /**
509          * Notifies the class of new DNS server information.
510          * @param lifetime the time in seconds that the DNS servers are valid.
511          * @param addresses the string representations of the IP addresses of DNS servers to use.
512          */
addServers(long lifetime, String[] addresses)513         public synchronized boolean addServers(long lifetime, String[] addresses) {
514             // If the servers are below the minimum lifetime, don't change anything.
515             if (lifetime != 0 && lifetime < mMinLifetime) return false;
516 
517             // The lifetime is actually an unsigned 32-bit number, but Java doesn't have unsigned.
518             // Technically 0xffffffff (the maximum) is special and means "forever", but 2^32 seconds
519             // (136 years) is close enough.
520             long now = System.currentTimeMillis();
521             long expiry = now + 1000 * lifetime;
522 
523             // Go through the list of servers. For each one, update the entry if one exists, and
524             // create one if it doesn't.
525             for (String addressString : addresses) {
526                 InetAddress address;
527                 try {
528                     address = InetAddresses.parseNumericAddress(addressString);
529                 } catch (IllegalArgumentException ex) {
530                     continue;
531                 }
532 
533                 if (!updateExistingEntry(address, expiry)) {
534                     // There was no entry for this server. Create one, unless it's already expired
535                     // (i.e., if the lifetime is zero; it cannot be < 0 because it's unsigned).
536                     if (expiry > now) {
537                         DnsServerEntry entry = new DnsServerEntry(address, expiry);
538                         mAllServers.add(entry);
539                         mIndex.put(address, entry);
540                     }
541                 }
542             }
543 
544             // Sort the servers by expiry.
545             Collections.sort(mAllServers);
546 
547             // Prune excess entries and update the current server list.
548             return updateCurrentServers();
549         }
550 
updateExistingEntry(InetAddress address, long expiry)551         private synchronized boolean updateExistingEntry(InetAddress address, long expiry) {
552             DnsServerEntry existing = mIndex.get(address);
553             if (existing != null) {
554                 existing.expiry = expiry;
555                 return true;
556             }
557             return false;
558         }
559 
updateCurrentServers()560         private synchronized boolean updateCurrentServers() {
561             long now = System.currentTimeMillis();
562             boolean changed = false;
563 
564             // Prune excess or expired entries.
565             for (int i = mAllServers.size() - 1; i >= 0; i--) {
566                 if (i >= NUM_SERVERS || mAllServers.get(i).expiry <= now) {
567                     DnsServerEntry removed = mAllServers.remove(i);
568                     mIndex.remove(removed.address);
569                     changed |= mCurrentServers.remove(removed.address);
570                 } else {
571                     break;
572                 }
573             }
574 
575             // Add servers to the current set, in order of decreasing lifetime, until it has enough.
576             // Prefer existing servers over new servers in order to minimize updates to the rest of
577             // the system and avoid persistent oscillations.
578             for (DnsServerEntry entry : mAllServers) {
579                 if (mCurrentServers.size() < NUM_CURRENT_SERVERS) {
580                     changed |= mCurrentServers.add(entry.address);
581                 } else {
582                     break;
583                 }
584             }
585             return changed;
586         }
587     }
588 
589     /**
590      * Represents a DNS server entry with an expiry time.
591      *
592      * Implements Comparable so DNS server entries can be sorted by lifetime, longest-lived first.
593      * The ordering of entries with the same lifetime is unspecified, because given two servers with
594      * identical lifetimes, we don't care which one we use, and only comparing the lifetime is much
595      * faster than comparing the IP address as well.
596      *
597      * Note: this class has a natural ordering that is inconsistent with equals.
598      */
599     private static class DnsServerEntry implements Comparable<DnsServerEntry> {
600         /** The IP address of the DNS server. */
601         public final InetAddress address;
602         /** The time until which the DNS server may be used. A Java millisecond time as might be
603          * returned by currentTimeMillis(). */
604         public long expiry;
605 
DnsServerEntry(InetAddress address, long expiry)606         DnsServerEntry(InetAddress address, long expiry) throws IllegalArgumentException {
607             this.address = address;
608             this.expiry = expiry;
609         }
610 
compareTo(DnsServerEntry other)611         public int compareTo(DnsServerEntry other) {
612             return Long.compare(other.expiry, this.expiry);
613         }
614     }
615 }
616