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
17package com.android.server.net;
18
19import android.net.LinkAddress;
20import android.net.LinkProperties;
21import android.net.RouteInfo;
22import android.util.Log;
23
24import java.net.InetAddress;
25import java.util.ArrayList;
26import java.util.Arrays;
27import java.util.Collections;
28import java.util.HashMap;
29import java.util.HashSet;
30import 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 */
67public 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 */
214class 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 */
263class 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