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