Home | History | Annotate | Download | only in net
      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