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