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