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 com.android.server.net; 18 19 import android.net.LinkAddress; 20 import android.net.LinkProperties; 21 import android.net.RouteInfo; 22 import android.util.Log; 23 24 import java.net.InetAddress; 25 import java.util.ArrayList; 26 import java.util.Arrays; 27 import java.util.Collection; 28 import java.util.Collections; 29 import java.util.HashMap; 30 import java.util.HashSet; 31 import java.util.Objects; 32 import java.util.Set; 33 34 /** 35 * Keeps track of link configuration received from Netlink. 36 * 37 * Instances of this class are expected to be owned by subsystems such as Wi-Fi 38 * or Ethernet that manage one or more network interfaces. Each interface to be 39 * tracked needs its own {@code NetlinkTracker}. 40 * 41 * An instance of this class is constructed by passing in an interface name and 42 * a callback. The owner is then responsible for registering the tracker with 43 * NetworkManagementService. When the class receives update notifications from 44 * the NetworkManagementService notification threads, it applies the update to 45 * its local LinkProperties, and if something has changed, notifies its owner of 46 * the update via the callback. 47 * 48 * The owner can then call {@code getLinkProperties()} in order to find out 49 * what changed. If in the meantime the LinkProperties stored here have changed, 50 * this class will return the current LinkProperties. Because each change 51 * triggers an update callback after the change is made, the owner may get more 52 * callbacks than strictly necessary (some of which may be no-ops), but will not 53 * be out of sync once all callbacks have been processed. 54 * 55 * Threading model: 56 * 57 * - The owner of this class is expected to create it, register it, and call 58 * getLinkProperties or clearLinkProperties on its thread. 59 * - Most of the methods in the class are inherited from BaseNetworkObserver 60 * and are called by NetworkManagementService notification threads. 61 * - All accesses to mLinkProperties must be synchronized(this). All the other 62 * member variables are immutable once the object is constructed. 63 * 64 * This class currently tracks IPv4 and IPv6 addresses. In the future it will 65 * track routes and DNS servers. 66 * 67 * @hide 68 */ 69 public class NetlinkTracker extends BaseNetworkObserver { 70 71 private final String TAG; 72 73 public interface Callback { 74 public void update(); 75 } 76 77 private final String mInterfaceName; 78 private final Callback mCallback; 79 private final LinkProperties mLinkProperties; 80 private DnsServerRepository mDnsServerRepository; 81 82 private static final boolean DBG = false; 83 84 public NetlinkTracker(String iface, Callback callback) { 85 TAG = "NetlinkTracker/" + iface; 86 mInterfaceName = iface; 87 mCallback = callback; 88 mLinkProperties = new LinkProperties(); 89 mLinkProperties.setInterfaceName(mInterfaceName); 90 mDnsServerRepository = new DnsServerRepository(); 91 } 92 93 private void maybeLog(String operation, String iface, LinkAddress address) { 94 if (DBG) { 95 Log.d(TAG, operation + ": " + address + " on " + iface + 96 " flags " + address.getFlags() + " scope " + address.getScope()); 97 } 98 } 99 100 private void maybeLog(String operation, Object o) { 101 if (DBG) { 102 Log.d(TAG, operation + ": " + o.toString()); 103 } 104 } 105 106 @Override 107 public void addressUpdated(String iface, LinkAddress address) { 108 if (mInterfaceName.equals(iface)) { 109 maybeLog("addressUpdated", iface, address); 110 boolean changed; 111 synchronized (this) { 112 changed = mLinkProperties.addLinkAddress(address); 113 } 114 if (changed) { 115 mCallback.update(); 116 } 117 } 118 } 119 120 @Override 121 public void addressRemoved(String iface, LinkAddress address) { 122 if (mInterfaceName.equals(iface)) { 123 maybeLog("addressRemoved", iface, address); 124 boolean changed; 125 synchronized (this) { 126 changed = mLinkProperties.removeLinkAddress(address); 127 } 128 if (changed) { 129 mCallback.update(); 130 } 131 } 132 } 133 134 @Override 135 public void routeUpdated(RouteInfo route) { 136 if (mInterfaceName.equals(route.getInterface())) { 137 maybeLog("routeUpdated", route); 138 boolean changed; 139 synchronized (this) { 140 changed = mLinkProperties.addRoute(route); 141 } 142 if (changed) { 143 mCallback.update(); 144 } 145 } 146 } 147 148 @Override 149 public void routeRemoved(RouteInfo route) { 150 if (mInterfaceName.equals(route.getInterface())) { 151 maybeLog("routeRemoved", route); 152 boolean changed; 153 synchronized (this) { 154 changed = mLinkProperties.removeRoute(route); 155 } 156 if (changed) { 157 mCallback.update(); 158 } 159 } 160 } 161 162 @Override 163 public void interfaceDnsServerInfo(String iface, long lifetime, String[] addresses) { 164 if (mInterfaceName.equals(iface)) { 165 maybeLog("interfaceDnsServerInfo", Arrays.toString(addresses)); 166 boolean changed = mDnsServerRepository.addServers(lifetime, addresses); 167 if (changed) { 168 synchronized (this) { 169 mDnsServerRepository.setDnsServersOn(mLinkProperties); 170 } 171 mCallback.update(); 172 } 173 } 174 } 175 176 /** 177 * Returns a copy of this object's LinkProperties. 178 */ 179 public synchronized LinkProperties getLinkProperties() { 180 return new LinkProperties(mLinkProperties); 181 } 182 183 public synchronized void clearLinkProperties() { 184 // Clear the repository before clearing mLinkProperties. That way, if a clear() happens 185 // while interfaceDnsServerInfo() is being called, we'll end up with no DNS servers in 186 // mLinkProperties, as desired. 187 mDnsServerRepository = new DnsServerRepository(); 188 mLinkProperties.clear(); 189 mLinkProperties.setInterfaceName(mInterfaceName); 190 } 191 } 192 193 /** 194 * Represents a DNS server entry with an expiry time. 195 * 196 * Implements Comparable so DNS server entries can be sorted by lifetime, longest-lived first. 197 * The ordering of entries with the same lifetime is unspecified, because given two servers with 198 * identical lifetimes, we don't care which one we use, and only comparing the lifetime is much 199 * faster than comparing the IP address as well. 200 * 201 * Note: this class has a natural ordering that is inconsistent with equals. 202 */ 203 class DnsServerEntry implements Comparable<DnsServerEntry> { 204 /** The IP address of the DNS server. */ 205 public final InetAddress address; 206 /** The time until which the DNS server may be used. A Java millisecond time as might be 207 * returned by currentTimeMillis(). */ 208 public long expiry; 209 210 public DnsServerEntry(InetAddress address, long expiry) throws IllegalArgumentException { 211 this.address = address; 212 this.expiry = expiry; 213 } 214 215 public int compareTo(DnsServerEntry other) { 216 return Long.compare(other.expiry, this.expiry); 217 } 218 } 219 220 /** 221 * Tracks DNS server updates received from Netlink. 222 * 223 * The network may announce an arbitrary number of DNS servers in Router Advertisements at any 224 * time. Each announcement has a lifetime; when the lifetime expires, the servers should not be used 225 * any more. In this way, the network can gracefully migrate clients from one set of DNS servers to 226 * another. Announcements can both raise and lower the lifetime, and an announcement can expire 227 * servers by announcing them with a lifetime of zero. 228 * 229 * Typically the system will only use a small number (2 or 3; {@code NUM_CURRENT_SERVERS}) of DNS 230 * servers at any given time. These are referred to as the current servers. In case all the 231 * current servers expire, the class also keeps track of a larger (but limited) number of servers 232 * that are promoted to current servers when the current ones expire. In order to minimize updates 233 * to the rest of the system (and potentially expensive cache flushes) this class attempts to keep 234 * the list of current servers constant where possible. More specifically, the list of current 235 * servers is only updated if a new server is learned and there are not yet {@code 236 * NUM_CURRENT_SERVERS} current servers, or if one or more of the current servers expires or is 237 * pushed out of the set. Therefore, the current servers will not necessarily be the ones with the 238 * highest lifetime, but the ones learned first. 239 * 240 * This is by design: if instead the class always preferred the servers with the highest lifetime, a 241 * (misconfigured?) network where two or more routers announce more than {@code NUM_CURRENT_SERVERS} 242 * unique servers would cause persistent oscillations. 243 * 244 * TODO: Currently servers are only expired when a new DNS update is received. 245 * Update them using timers, or possibly on every notification received by NetlinkTracker. 246 * 247 * Threading model: run by NetlinkTracker. Methods are synchronized(this) just in case netlink 248 * notifications are sent by multiple threads. If future threads use alarms to expire, those 249 * alarms must also be synchronized(this). 250 * 251 */ 252 class DnsServerRepository { 253 254 /** How many DNS servers we will use. 3 is suggested by RFC 6106. */ 255 public static final int NUM_CURRENT_SERVERS = 3; 256 257 /** How many DNS servers we'll keep track of, in total. */ 258 public static final int NUM_SERVERS = 12; 259 260 /** Stores up to {@code NUM_CURRENT_SERVERS} DNS servers we're currently using. */ 261 private Set<InetAddress> mCurrentServers; 262 263 public static final String TAG = "DnsServerRepository"; 264 265 /** 266 * Stores all the DNS servers we know about, for use when the current servers expire. 267 * Always sorted in order of decreasing expiry. The elements in this list are also the values 268 * of mIndex, and may be elements in mCurrentServers. 269 */ 270 private ArrayList<DnsServerEntry> mAllServers; 271 272 /** 273 * Indexes the servers so we can update their lifetimes more quickly in the common case where 274 * servers are not being added, but only being refreshed. 275 */ 276 private HashMap<InetAddress, DnsServerEntry> mIndex; 277 278 public DnsServerRepository() { 279 mCurrentServers = new HashSet(); 280 mAllServers = new ArrayList<DnsServerEntry>(NUM_SERVERS); 281 mIndex = new HashMap<InetAddress, DnsServerEntry>(NUM_SERVERS); 282 } 283 284 /** Sets the DNS servers of the provided LinkProperties object to the current servers. */ 285 public synchronized void setDnsServersOn(LinkProperties lp) { 286 lp.setDnsServers(mCurrentServers); 287 } 288 289 /** 290 * Notifies the class of new DNS server information. 291 * @param lifetime the time in seconds that the DNS servers are valid. 292 * @param addresses the string representations of the IP addresses of the DNS servers to use. 293 */ 294 public synchronized boolean addServers(long lifetime, String[] addresses) { 295 // The lifetime is actually an unsigned 32-bit number, but Java doesn't have unsigned. 296 // Technically 0xffffffff (the maximum) is special and means "forever", but 2^32 seconds 297 // (136 years) is close enough. 298 long now = System.currentTimeMillis(); 299 long expiry = now + 1000 * lifetime; 300 301 // Go through the list of servers. For each one, update the entry if one exists, and 302 // create one if it doesn't. 303 for (String addressString : addresses) { 304 InetAddress address; 305 try { 306 address = InetAddress.parseNumericAddress(addressString); 307 } catch (IllegalArgumentException ex) { 308 continue; 309 } 310 311 if (!updateExistingEntry(address, expiry)) { 312 // There was no entry for this server. Create one, unless it's already expired 313 // (i.e., if the lifetime is zero; it cannot be < 0 because it's unsigned). 314 if (expiry > now) { 315 DnsServerEntry entry = new DnsServerEntry(address, expiry); 316 mAllServers.add(entry); 317 mIndex.put(address, entry); 318 } 319 } 320 } 321 322 // Sort the servers by expiry. 323 Collections.sort(mAllServers); 324 325 // Prune excess entries and update the current server list. 326 return updateCurrentServers(); 327 } 328 329 private synchronized boolean updateExistingEntry(InetAddress address, long expiry) { 330 DnsServerEntry existing = mIndex.get(address); 331 if (existing != null) { 332 existing.expiry = expiry; 333 return true; 334 } 335 return false; 336 } 337 338 private synchronized boolean updateCurrentServers() { 339 long now = System.currentTimeMillis(); 340 boolean changed = false; 341 342 // Prune excess or expired entries. 343 for (int i = mAllServers.size() - 1; i >= 0; i--) { 344 if (i >= NUM_SERVERS || mAllServers.get(i).expiry < now) { 345 DnsServerEntry removed = mAllServers.remove(i); 346 mIndex.remove(removed.address); 347 changed |= mCurrentServers.remove(removed.address); 348 } else { 349 break; 350 } 351 } 352 353 // Add servers to the current set, in order of decreasing lifetime, until it has enough. 354 // Prefer existing servers over new servers in order to minimize updates to the rest of the 355 // system and avoid persistent oscillations. 356 for (DnsServerEntry entry : mAllServers) { 357 if (mCurrentServers.size() < NUM_CURRENT_SERVERS) { 358 changed |= mCurrentServers.add(entry.address); 359 } else { 360 break; 361 } 362 } 363 return changed; 364 } 365 } 366