Loading core/java/com/android/server/net/NetlinkTracker.java +204 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,16 @@ import android.net.LinkProperties; import android.net.RouteInfo; import android.util.Log; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Objects; import java.util.Set; /** * Keeps track of link configuration received from Netlink. * Loading Loading @@ -67,6 +77,7 @@ public class NetlinkTracker extends BaseNetworkObserver { private final String mInterfaceName; private final Callback mCallback; private final LinkProperties mLinkProperties; private DnsServerRepository mDnsServerRepository; private static final boolean DBG = true; Loading @@ -76,6 +87,7 @@ public class NetlinkTracker extends BaseNetworkObserver { mCallback = callback; mLinkProperties = new LinkProperties(); mLinkProperties.setInterfaceName(mInterfaceName); mDnsServerRepository = new DnsServerRepository(); } private void maybeLog(String operation, String iface, LinkAddress address) { Loading Loading @@ -147,6 +159,20 @@ public class NetlinkTracker extends BaseNetworkObserver { } } @Override public void interfaceDnsServerInfo(String iface, long lifetime, String[] addresses) { if (mInterfaceName.equals(iface)) { maybeLog("interfaceDnsServerInfo", Arrays.toString(addresses)); boolean changed = mDnsServerRepository.addServers(lifetime, addresses); if (changed) { synchronized (this) { mDnsServerRepository.setDnsServersOn(mLinkProperties); } mCallback.update(); } } } /** * Returns a copy of this object's LinkProperties. */ Loading @@ -155,7 +181,185 @@ public class NetlinkTracker extends BaseNetworkObserver { } public synchronized void clearLinkProperties() { // Clear the repository before clearing mLinkProperties. That way, if a clear() happens // while interfaceDnsServerInfo() is being called, we'll end up with no DNS servers in // mLinkProperties, as desired. mDnsServerRepository = new DnsServerRepository(); mLinkProperties.clear(); mLinkProperties.setInterfaceName(mInterfaceName); } } /** * Represents a DNS server entry with an expiry time. * * Implements Comparable so DNS server entries can be sorted by lifetime, longest-lived first. * The ordering of entries with the same lifetime is unspecified, because given two servers with * identical lifetimes, we don't care which one we use, and only comparing the lifetime is much * faster than comparing the IP address as well. * * Note: this class has a natural ordering that is inconsistent with equals. */ class DnsServerEntry implements Comparable<DnsServerEntry> { /** The IP address of the DNS server. */ public final InetAddress address; /** The time until which the DNS server may be used. A Java millisecond time as might be * returned by currentTimeMillis(). */ public long expiry; public DnsServerEntry(InetAddress address, long expiry) throws IllegalArgumentException { this.address = address; this.expiry = expiry; } public int compareTo(DnsServerEntry other) { return Long.compare(other.expiry, this.expiry); } } /** * Tracks DNS server updates received from Netlink. * * The network may announce an arbitrary number of DNS servers in Router Advertisements at any * time. Each announcement has a lifetime; when the lifetime expires, the servers should not be used * any more. In this way, the network can gracefully migrate clients from one set of DNS servers to * another. Announcements can both raise and lower the lifetime, and an announcement can expire * servers by announcing them with a lifetime of zero. * * Typically the system will only use a small number (2 or 3; {@code NUM_CURRENT_SERVERS}) of DNS * servers at any given time. These are referred to as the current servers. In case all the * current servers expire, the class also keeps track of a larger (but limited) number of servers * that are promoted to current servers when the current ones expire. In order to minimize updates * to the rest of the system (and potentially expensive cache flushes) this class attempts to keep * the list of current servers constant where possible. More specifically, the list of current * servers is only updated if a new server is learned and there are not yet {@code * NUM_CURRENT_SERVERS} current servers, or if one or more of the current servers expires or is * pushed out of the set. Therefore, the current servers will not necessarily be the ones with the * highest lifetime, but the ones learned first. * * This is by design: if instead the class always preferred the servers with the highest lifetime, a * (misconfigured?) network where two or more routers announce more than {@code NUM_CURRENT_SERVERS} * unique servers would cause persistent oscillations. * * TODO: Currently servers are only expired when a new DNS update is received. * Update them using timers, or possibly on every notification received by NetlinkTracker. * * Threading model: run by NetlinkTracker. Methods are synchronized(this) just in case netlink * notifications are sent by multiple threads. If future threads use alarms to expire, those * alarms must also be synchronized(this). * */ class DnsServerRepository { /** How many DNS servers we will use. 3 is suggested by RFC 6106. */ public static final int NUM_CURRENT_SERVERS = 3; /** How many DNS servers we'll keep track of, in total. */ public static final int NUM_SERVERS = 12; /** Stores up to {@code NUM_CURRENT_SERVERS} DNS servers we're currently using. */ private Set<InetAddress> mCurrentServers; public static final String TAG = "DnsServerRepository"; /** * Stores all the DNS servers we know about, for use when the current servers expire. * Always sorted in order of decreasing expiry. The elements in this list are also the values * of mIndex, and may be elements in mCurrentServers. */ private ArrayList<DnsServerEntry> mAllServers; /** * Indexes the servers so we can update their lifetimes more quickly in the common case where * servers are not being added, but only being refreshed. */ private HashMap<InetAddress, DnsServerEntry> mIndex; public DnsServerRepository() { mCurrentServers = new HashSet(); mAllServers = new ArrayList<DnsServerEntry>(NUM_SERVERS); mIndex = new HashMap<InetAddress, DnsServerEntry>(NUM_SERVERS); } /** Sets the DNS servers of the provided LinkProperties object to the current servers. */ public synchronized void setDnsServersOn(LinkProperties lp) { lp.setDnsServers(mCurrentServers); } /** * Notifies the class of new DNS server information. * @param lifetime the time in seconds that the DNS servers are valid. * @param addresses the string representations of the IP addresses of the DNS servers to use. */ public synchronized boolean addServers(long lifetime, String[] addresses) { // The lifetime is actually an unsigned 32-bit number, but Java doesn't have unsigned. // Technically 0xffffffff (the maximum) is special and means "forever", but 2^32 seconds // (136 years) is close enough. long now = System.currentTimeMillis(); long expiry = now + 1000 * lifetime; // Go through the list of servers. For each one, update the entry if one exists, and // create one if it doesn't. for (String addressString : addresses) { InetAddress address; try { address = InetAddress.parseNumericAddress(addressString); } catch (IllegalArgumentException ex) { continue; } if (!updateExistingEntry(address, expiry)) { // There was no entry for this server. Create one, unless it's already expired // (i.e., if the lifetime is zero; it cannot be < 0 because it's unsigned). if (expiry > now) { DnsServerEntry entry = new DnsServerEntry(address, expiry); mAllServers.add(entry); mIndex.put(address, entry); } } } // Sort the servers by expiry. Collections.sort(mAllServers); // Prune excess entries and update the current server list. return updateCurrentServers(); } private synchronized boolean updateExistingEntry(InetAddress address, long expiry) { DnsServerEntry existing = mIndex.get(address); if (existing != null) { existing.expiry = expiry; return true; } return false; } private synchronized boolean updateCurrentServers() { long now = System.currentTimeMillis(); boolean changed = false; // Prune excess or expired entries. for (int i = mAllServers.size() - 1; i >= 0; i--) { if (i >= NUM_SERVERS || mAllServers.get(i).expiry < now) { DnsServerEntry removed = mAllServers.remove(i); mIndex.remove(removed.address); changed |= mCurrentServers.remove(removed.address); } else { break; } } // Add servers to the current set, in order of decreasing lifetime, until it has enough. // Prefer existing servers over new servers in order to minimize updates to the rest of the // system and avoid persistent oscillations. for (DnsServerEntry entry : mAllServers) { if (mCurrentServers.size() < NUM_CURRENT_SERVERS) { changed |= mCurrentServers.add(entry.address); } else { break; } } return changed; } } Loading
core/java/com/android/server/net/NetlinkTracker.java +204 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,16 @@ import android.net.LinkProperties; import android.net.RouteInfo; import android.util.Log; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Objects; import java.util.Set; /** * Keeps track of link configuration received from Netlink. * Loading Loading @@ -67,6 +77,7 @@ public class NetlinkTracker extends BaseNetworkObserver { private final String mInterfaceName; private final Callback mCallback; private final LinkProperties mLinkProperties; private DnsServerRepository mDnsServerRepository; private static final boolean DBG = true; Loading @@ -76,6 +87,7 @@ public class NetlinkTracker extends BaseNetworkObserver { mCallback = callback; mLinkProperties = new LinkProperties(); mLinkProperties.setInterfaceName(mInterfaceName); mDnsServerRepository = new DnsServerRepository(); } private void maybeLog(String operation, String iface, LinkAddress address) { Loading Loading @@ -147,6 +159,20 @@ public class NetlinkTracker extends BaseNetworkObserver { } } @Override public void interfaceDnsServerInfo(String iface, long lifetime, String[] addresses) { if (mInterfaceName.equals(iface)) { maybeLog("interfaceDnsServerInfo", Arrays.toString(addresses)); boolean changed = mDnsServerRepository.addServers(lifetime, addresses); if (changed) { synchronized (this) { mDnsServerRepository.setDnsServersOn(mLinkProperties); } mCallback.update(); } } } /** * Returns a copy of this object's LinkProperties. */ Loading @@ -155,7 +181,185 @@ public class NetlinkTracker extends BaseNetworkObserver { } public synchronized void clearLinkProperties() { // Clear the repository before clearing mLinkProperties. That way, if a clear() happens // while interfaceDnsServerInfo() is being called, we'll end up with no DNS servers in // mLinkProperties, as desired. mDnsServerRepository = new DnsServerRepository(); mLinkProperties.clear(); mLinkProperties.setInterfaceName(mInterfaceName); } } /** * Represents a DNS server entry with an expiry time. * * Implements Comparable so DNS server entries can be sorted by lifetime, longest-lived first. * The ordering of entries with the same lifetime is unspecified, because given two servers with * identical lifetimes, we don't care which one we use, and only comparing the lifetime is much * faster than comparing the IP address as well. * * Note: this class has a natural ordering that is inconsistent with equals. */ class DnsServerEntry implements Comparable<DnsServerEntry> { /** The IP address of the DNS server. */ public final InetAddress address; /** The time until which the DNS server may be used. A Java millisecond time as might be * returned by currentTimeMillis(). */ public long expiry; public DnsServerEntry(InetAddress address, long expiry) throws IllegalArgumentException { this.address = address; this.expiry = expiry; } public int compareTo(DnsServerEntry other) { return Long.compare(other.expiry, this.expiry); } } /** * Tracks DNS server updates received from Netlink. * * The network may announce an arbitrary number of DNS servers in Router Advertisements at any * time. Each announcement has a lifetime; when the lifetime expires, the servers should not be used * any more. In this way, the network can gracefully migrate clients from one set of DNS servers to * another. Announcements can both raise and lower the lifetime, and an announcement can expire * servers by announcing them with a lifetime of zero. * * Typically the system will only use a small number (2 or 3; {@code NUM_CURRENT_SERVERS}) of DNS * servers at any given time. These are referred to as the current servers. In case all the * current servers expire, the class also keeps track of a larger (but limited) number of servers * that are promoted to current servers when the current ones expire. In order to minimize updates * to the rest of the system (and potentially expensive cache flushes) this class attempts to keep * the list of current servers constant where possible. More specifically, the list of current * servers is only updated if a new server is learned and there are not yet {@code * NUM_CURRENT_SERVERS} current servers, or if one or more of the current servers expires or is * pushed out of the set. Therefore, the current servers will not necessarily be the ones with the * highest lifetime, but the ones learned first. * * This is by design: if instead the class always preferred the servers with the highest lifetime, a * (misconfigured?) network where two or more routers announce more than {@code NUM_CURRENT_SERVERS} * unique servers would cause persistent oscillations. * * TODO: Currently servers are only expired when a new DNS update is received. * Update them using timers, or possibly on every notification received by NetlinkTracker. * * Threading model: run by NetlinkTracker. Methods are synchronized(this) just in case netlink * notifications are sent by multiple threads. If future threads use alarms to expire, those * alarms must also be synchronized(this). * */ class DnsServerRepository { /** How many DNS servers we will use. 3 is suggested by RFC 6106. */ public static final int NUM_CURRENT_SERVERS = 3; /** How many DNS servers we'll keep track of, in total. */ public static final int NUM_SERVERS = 12; /** Stores up to {@code NUM_CURRENT_SERVERS} DNS servers we're currently using. */ private Set<InetAddress> mCurrentServers; public static final String TAG = "DnsServerRepository"; /** * Stores all the DNS servers we know about, for use when the current servers expire. * Always sorted in order of decreasing expiry. The elements in this list are also the values * of mIndex, and may be elements in mCurrentServers. */ private ArrayList<DnsServerEntry> mAllServers; /** * Indexes the servers so we can update their lifetimes more quickly in the common case where * servers are not being added, but only being refreshed. */ private HashMap<InetAddress, DnsServerEntry> mIndex; public DnsServerRepository() { mCurrentServers = new HashSet(); mAllServers = new ArrayList<DnsServerEntry>(NUM_SERVERS); mIndex = new HashMap<InetAddress, DnsServerEntry>(NUM_SERVERS); } /** Sets the DNS servers of the provided LinkProperties object to the current servers. */ public synchronized void setDnsServersOn(LinkProperties lp) { lp.setDnsServers(mCurrentServers); } /** * Notifies the class of new DNS server information. * @param lifetime the time in seconds that the DNS servers are valid. * @param addresses the string representations of the IP addresses of the DNS servers to use. */ public synchronized boolean addServers(long lifetime, String[] addresses) { // The lifetime is actually an unsigned 32-bit number, but Java doesn't have unsigned. // Technically 0xffffffff (the maximum) is special and means "forever", but 2^32 seconds // (136 years) is close enough. long now = System.currentTimeMillis(); long expiry = now + 1000 * lifetime; // Go through the list of servers. For each one, update the entry if one exists, and // create one if it doesn't. for (String addressString : addresses) { InetAddress address; try { address = InetAddress.parseNumericAddress(addressString); } catch (IllegalArgumentException ex) { continue; } if (!updateExistingEntry(address, expiry)) { // There was no entry for this server. Create one, unless it's already expired // (i.e., if the lifetime is zero; it cannot be < 0 because it's unsigned). if (expiry > now) { DnsServerEntry entry = new DnsServerEntry(address, expiry); mAllServers.add(entry); mIndex.put(address, entry); } } } // Sort the servers by expiry. Collections.sort(mAllServers); // Prune excess entries and update the current server list. return updateCurrentServers(); } private synchronized boolean updateExistingEntry(InetAddress address, long expiry) { DnsServerEntry existing = mIndex.get(address); if (existing != null) { existing.expiry = expiry; return true; } return false; } private synchronized boolean updateCurrentServers() { long now = System.currentTimeMillis(); boolean changed = false; // Prune excess or expired entries. for (int i = mAllServers.size() - 1; i >= 0; i--) { if (i >= NUM_SERVERS || mAllServers.get(i).expiry < now) { DnsServerEntry removed = mAllServers.remove(i); mIndex.remove(removed.address); changed |= mCurrentServers.remove(removed.address); } else { break; } } // Add servers to the current set, in order of decreasing lifetime, until it has enough. // Prefer existing servers over new servers in order to minimize updates to the rest of the // system and avoid persistent oscillations. for (DnsServerEntry entry : mAllServers) { if (mCurrentServers.size() < NUM_CURRENT_SERVERS) { changed |= mCurrentServers.add(entry.address); } else { break; } } return changed; } }