From fa37b2f6135a532742ef8242e70dbf499abe8d95 Mon Sep 17 00:00:00 2001 From: Erik Kline Date: Tue, 2 Aug 2016 18:27:03 +0900 Subject: [PATCH] Handle setting and deprecating local DNS IPv6 addresses [ IPv6TetheringCoordinator ] - Send CMD_IPV6_TETHER_UPDATE to all TetherInterfaceStateMachines. This results in some spurious noop messages, but currently the coordinator does not maintain state about which downstream interaces are serving IPv6 and which are not. Until it does maintain such state it does not know to notify only the affected interfaces about a loss of IPv6 upstream [ IPv6TetheringInterfaceServices ] - De-dup incoming IPv6-only LinkProperties to avoid spurious updates to the RA daemon. - Keep track of new prefixes/dns and newly deprecated prefixes/dns on each update. - Set and remove local_network routes based on new and newly deprecated prefixes. - Set and remove local DNS server IP addresses based on new and newly deprecated DNS servers. Update dnsmasq via netd accordingly. - Pass newly deprecated and new information to the RA daemon. [ RouterAdvertisementDaemon ] - Receive newly deprecated prefixes/DNS and update local tracking data structures accordingly. - Properly send urgent final deprecation RAs, and then cease all transmissions. Bug: 30298058 Change-Id: Idc821603fa93bff1a821d1180d4135bc70e4a08d --- .../server/connectivity/Tethering.java | 10 + .../tethering/IPv6TetheringCoordinator.java | 18 +- .../IPv6TetheringInterfaceServices.java | 226 +++++++++++------ .../net/ip/RouterAdvertisementDaemon.java | 234 ++++++++++++------ 4 files changed, 329 insertions(+), 159 deletions(-) diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index b6c8d5d87b4c4..e0d8373fc445a 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -1392,6 +1392,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering for (Integer netType : mUpstreamIfaceTypes) { NetworkInfo info = cm.getNetworkInfo(netType.intValue()); + // TODO: if the network is suspended we should consider + // that to be the same as connected here. if ((info != null) && info.isConnected()) { upType = netType.intValue(); break; @@ -1465,6 +1467,10 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering // it immediately, because there likely will be no second // EVENT_ON_AVAILABLE (it was already received). handleNewUpstreamNetworkState(ns); + } else if (mCurrentUpstreamIface == null) { + // There are no available upstream networks, or none that + // have an IPv4 default route (current metric for success). + handleNewUpstreamNetworkState(null); } } @@ -1639,6 +1645,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering chooseUpstreamType(mTryCell); mTryCell = !mTryCell; } + @Override public void exit() { // TODO: examine if we should check the return value. @@ -1646,7 +1653,9 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering mUpstreamNetworkMonitor.stop(); stopListeningForSimChanges(); notifyTetheredOfNewUpstreamIface(null); + handleNewUpstreamNetworkState(null); } + @Override public boolean processMessage(Message message) { maybeLogMessage(this, message.what); @@ -1734,6 +1743,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering // reevaluation is triggered via received CONNECTIVITY_ACTION // broadcasts that result in being passed a // TetherMasterSM.CMD_UPSTREAM_CHANGED. + handleNewUpstreamNetworkState(null); break; default: break; diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java index 8254397863603..e94b584ac48aa 100644 --- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java +++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java @@ -55,7 +55,7 @@ public class IPv6TetheringCoordinator { if (VDBG) { Log.d(TAG, "updateUpstreamNetworkState: " + toDebugString(ns)); } - if (ns == null || ns.network == null) { + if (!canTetherIPv6(ns)) { stopIPv6TetheringOnAllInterfaces(); setUpstreamNetworkState(null); return; @@ -65,8 +65,9 @@ public class IPv6TetheringCoordinator { !ns.network.equals(mUpstreamNetworkState.network)) { stopIPv6TetheringOnAllInterfaces(); } + setUpstreamNetworkState(ns); - maybeUpdateIPv6TetheringInterfaces(); + updateIPv6TetheringInterfaces(); } private void stopIPv6TetheringOnAllInterfaces() { @@ -77,9 +78,10 @@ public class IPv6TetheringCoordinator { } private void setUpstreamNetworkState(NetworkState ns) { - if (!canTetherIPv6(ns)) { + if (ns == null) { mUpstreamNetworkState = null; } else { + // Make a deep copy of the parts we need. mUpstreamNetworkState = new NetworkState( null, new LinkProperties(ns.linkProperties), @@ -94,19 +96,17 @@ public class IPv6TetheringCoordinator { } } - private void maybeUpdateIPv6TetheringInterfaces() { - if (mUpstreamNetworkState == null) return; - + private void updateIPv6TetheringInterfaces() { for (TetherInterfaceStateMachine sm : mNotifyList) { final LinkProperties lp = getInterfaceIPv6LinkProperties(sm.interfaceType()); - if (lp != null) { - sm.sendMessage(TetherInterfaceStateMachine.CMD_IPV6_TETHER_UPDATE, 0, 0, lp); - } + sm.sendMessage(TetherInterfaceStateMachine.CMD_IPV6_TETHER_UPDATE, 0, 0, lp); break; } } private LinkProperties getInterfaceIPv6LinkProperties(int interfaceType) { + if (mUpstreamNetworkState == null) return null; + // NOTE: Here, in future, we would have policies to decide how to divvy // up the available dedicated prefixes among downstream interfaces. // At this time we have no such mechanism--we only support tethering diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java index b47e079395d82..d087f903560ac 100644 --- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java +++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java @@ -16,6 +16,7 @@ package com.android.server.connectivity.tethering; +import android.net.INetd; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; @@ -27,13 +28,16 @@ import android.net.ip.RouterAdvertisementDaemon.RaParams; import android.os.INetworkManagementService; import android.os.RemoteException; import android.util.Log; +import android.util.Slog; import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashSet; +import java.util.Objects; /** @@ -41,13 +45,15 @@ import java.util.HashSet; */ class IPv6TetheringInterfaceServices { private static final String TAG = IPv6TetheringInterfaceServices.class.getSimpleName(); + private static final IpPrefix LINK_LOCAL_PREFIX = new IpPrefix("fe80::/64"); + private static final int RFC7421_IP_PREFIX_LENGTH = 64; private final String mIfName; private final INetworkManagementService mNMService; private NetworkInterface mNetworkInterface; private byte[] mHwAddr; - private ArrayList mLastLocalRoutes; + private LinkProperties mLastIPv6LinkProperties; private RouterAdvertisementDaemon mRaDaemon; private RaParams mLastRaParams; @@ -86,8 +92,7 @@ class IPv6TetheringInterfaceServices { public void stop() { mNetworkInterface = null; mHwAddr = null; - updateLocalRoutes(null); - updateRaParams(null); + setRaParams(null); if (mRaDaemon != null) { mRaDaemon.stop(); @@ -104,95 +109,178 @@ class IPv6TetheringInterfaceServices { public void updateUpstreamIPv6LinkProperties(LinkProperties v6only) { if (mRaDaemon == null) return; - if (v6only == null) { - updateLocalRoutes(null); - updateRaParams(null); + // Avoid unnecessary work on spurious updates. + if (Objects.equals(mLastIPv6LinkProperties, v6only)) { return; } - RaParams params = new RaParams(); - params.mtu = v6only.getMtu(); - params.hasDefaultRoute = v6only.hasIPv6DefaultRoute(); + RaParams params = null; - ArrayList localRoutes = new ArrayList(); - for (LinkAddress linkAddr : v6only.getLinkAddresses()) { - final IpPrefix prefix = new IpPrefix(linkAddr.getAddress(), - linkAddr.getPrefixLength()); + if (v6only != null) { + params = new RaParams(); + params.mtu = v6only.getMtu(); + params.hasDefaultRoute = v6only.hasIPv6DefaultRoute(); - // Accumulate routes representing "prefixes to be assigned to the - // local interface", for subsequent addition to the local network - // in the routing rules. - localRoutes.add(new RouteInfo(prefix, null, mIfName)); + for (LinkAddress linkAddr : v6only.getLinkAddresses()) { + if (linkAddr.getPrefixLength() != RFC7421_IP_PREFIX_LENGTH) continue; - params.prefixes.add(prefix); + final IpPrefix prefix = new IpPrefix( + linkAddr.getAddress(), linkAddr.getPrefixLength()); + params.prefixes.add(prefix); + + final Inet6Address dnsServer = getLocalDnsIpFor(prefix); + if (dnsServer != null) { + params.dnses.add(dnsServer); + } + } } + // If v6only is null, we pass in null to setRaParams(), which handles + // deprecation of any existing RA data. - // We need to be able to send unicast RAs, and clients might like to - // ping the default router's link-local address, so add that as well. - localRoutes.add(new RouteInfo(new IpPrefix("fe80::/64"), null, mIfName)); + setRaParams(params); + mLastIPv6LinkProperties = v6only; + } - // TODO: Add a local interface address, update dnsmasq to listen on the - // new address, and use only that address as a DNS server. - for (InetAddress dnsServer : v6only.getDnsServers()) { - if (dnsServer instanceof Inet6Address) { - params.dnses.add((Inet6Address) dnsServer); + + private void configureLocalRoutes( + HashSet deprecatedPrefixes, HashSet newPrefixes) { + // [1] Remove the routes that are deprecated. + if (!deprecatedPrefixes.isEmpty()) { + final ArrayList toBeRemoved = getLocalRoutesFor(deprecatedPrefixes); + try { + final int removalFailures = mNMService.removeRoutesFromLocalNetwork(toBeRemoved); + if (removalFailures > 0) { + Log.e(TAG, String.format("Failed to remove %d IPv6 routes from local table.", + removalFailures)); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to remove IPv6 routes from local table: ", e); } } - updateLocalRoutes(localRoutes); - updateRaParams(params); - } + // [2] Add only the routes that have not previously been added. + if (newPrefixes != null && !newPrefixes.isEmpty()) { + HashSet addedPrefixes = (HashSet) newPrefixes.clone(); + if (mLastRaParams != null) { + addedPrefixes.removeAll(mLastRaParams.prefixes); + } - private void updateLocalRoutes(ArrayList localRoutes) { - if (localRoutes != null) { - // TODO: Compare with mLastLocalRoutes and take appropriate - // appropriate action on the difference between the two. + if (mLastRaParams == null || mLastRaParams.prefixes.isEmpty()) { + // We need to be able to send unicast RAs, and clients might + // like to ping the default router's link-local address. Note + // that we never remove the link-local route from the network + // until Tethering disables tethering on the interface. + addedPrefixes.add(LINK_LOCAL_PREFIX); + } - if (!localRoutes.isEmpty()) { + if (!addedPrefixes.isEmpty()) { + final ArrayList toBeAdded = getLocalRoutesFor(addedPrefixes); try { - mNMService.addInterfaceToLocalNetwork(mIfName, localRoutes); + // It's safe to call addInterfaceToLocalNetwork() even if + // the interface is already in the local_network. + mNMService.addInterfaceToLocalNetwork(mIfName, toBeAdded); } catch (RemoteException e) { Log.e(TAG, "Failed to add IPv6 routes to local table: ", e); } } - } else { - if (mLastLocalRoutes != null && !mLastLocalRoutes.isEmpty()) { - try { - final int removalFailures = - mNMService.removeRoutesFromLocalNetwork(mLastLocalRoutes); - if (removalFailures > 0) { - Log.e(TAG, - String.format("Failed to remove %d IPv6 routes from local table.", - removalFailures)); - } - } catch (RemoteException e) { - Log.e(TAG, "Failed to remove IPv6 routes from local table: ", e); - } - } } - - mLastLocalRoutes = localRoutes; } - private void updateRaParams(RaParams params) { - if (mRaDaemon != null) { - HashSet deprecated = null; - - if (mLastRaParams != null) { - deprecated = new HashSet<>(); - - for (IpPrefix ipp : mLastRaParams.prefixes) { - if (params == null || !params.prefixes.contains(ipp)) { - deprecated.add(ipp); - } - } - } - - // Currently, we send spurious RAs (5) whenever there's any update. - // TODO: Compare params with mLastParams to avoid spurious updates. - mRaDaemon.buildNewRa(params, deprecated); + private void configureLocalDns( + HashSet deprecatedDnses, HashSet newDnses) { + INetd netd = getNetdServiceOrNull(); + if (netd == null) { + if (newDnses != null) newDnses.clear(); + Log.e(TAG, "No netd service instance available; not setting local IPv6 addresses"); + return; } - mLastRaParams = params; + // [1] Remove deprecated local DNS IP addresses. + if (!deprecatedDnses.isEmpty()) { + for (Inet6Address dns : deprecatedDnses) { + final String dnsString = dns.getHostAddress(); + try { + netd.interfaceDelAddress(mIfName, dnsString, RFC7421_IP_PREFIX_LENGTH); + } catch (RemoteException e) { + Log.e(TAG, "Failed to remove local dns IP: " + dnsString, e); + } + } + } + + // [2] Add only the local DNS IP addresses that have not previously been added. + if (newDnses != null && !newDnses.isEmpty()) { + final HashSet addedDnses = (HashSet) newDnses.clone(); + if (mLastRaParams != null) { + addedDnses.removeAll(mLastRaParams.dnses); + } + + for (Inet6Address dns : addedDnses) { + final String dnsString = dns.getHostAddress(); + try { + netd.interfaceAddAddress(mIfName, dnsString, RFC7421_IP_PREFIX_LENGTH); + } catch (RemoteException e) { + Log.e(TAG, "Failed to add local dns IP: " + dnsString, e); + newDnses.remove(dns); + } + } + } + + try { + netd.tetherApplyDnsInterfaces(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to update local DNS caching server"); + if (newDnses != null) newDnses.clear(); + } + } + + private void setRaParams(RaParams newParams) { + if (mRaDaemon != null) { + final RaParams deprecatedParams = + RaParams.getDeprecatedRaParams(mLastRaParams, newParams); + + configureLocalRoutes(deprecatedParams.prefixes, + (newParams != null) ? newParams.prefixes : null); + + configureLocalDns(deprecatedParams.dnses, + (newParams != null) ? newParams.dnses : null); + + mRaDaemon.buildNewRa(deprecatedParams, newParams); + } + + mLastRaParams = newParams; + } + + // Accumulate routes representing "prefixes to be assigned to the local + // interface", for subsequent modification of local_network routing. + private ArrayList getLocalRoutesFor(HashSet prefixes) { + final ArrayList localRoutes = new ArrayList(); + for (IpPrefix ipp : prefixes) { + localRoutes.add(new RouteInfo(ipp, null, mIfName)); + } + return localRoutes; + } + + private INetd getNetdServiceOrNull() { + if (mNMService != null) { + try { + return mNMService.getNetdService(); + } catch (RemoteException ignored) { + // This blocks until netd can be reached, but it can return + // null during a netd crash. + } + } + return null; + } + + // Given a prefix like 2001:db8::/64 return 2001:db8::1. + private static Inet6Address getLocalDnsIpFor(IpPrefix localPrefix) { + final byte[] dnsBytes = localPrefix.getRawAddress(); + dnsBytes[dnsBytes.length - 1] = 0x1; + try { + return Inet6Address.getByAddress(null, dnsBytes, 0); + } catch (UnknownHostException e) { + Slog.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix); + return null; + } } } diff --git a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java index 1a9d2f23935ed..a52cdcc98b9cb 100644 --- a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java +++ b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java @@ -47,6 +47,7 @@ import java.nio.ByteOrder; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.Map; import java.util.Random; import java.util.Set; @@ -61,10 +62,6 @@ import java.util.concurrent.atomic.AtomicInteger; * - Rewrite using Handler (and friends) so that AlarmManager can deliver * "kick" messages when it's time to send a multicast RA. * - * - Support transmitting MAX_URGENT_RTR_ADVERTISEMENTS number of empty - * RAs with zero default router lifetime when transitioning from an - * advertising state to a non-advertising state. - * * @hide */ public class RouterAdvertisementDaemon { @@ -112,8 +109,7 @@ public class RouterAdvertisementDaemon { @GuardedBy("mLock") private int mRaLength; @GuardedBy("mLock") - private final HashMap mDeprecatedPrefixes; - + private final DeprecatedInfoTracker mDeprecatedInfoTracker; @GuardedBy("mLock") private RaParams mRaParams; @@ -140,6 +136,88 @@ public class RouterAdvertisementDaemon { prefixes = (HashSet) other.prefixes.clone(); dnses = (HashSet) other.dnses.clone(); } + + // Returns the subset of RA parameters that become deprecated when + // moving from announcing oldRa to announcing newRa. + // + // Currently only tracks differences in |prefixes| and |dnses|. + public static RaParams getDeprecatedRaParams(RaParams oldRa, RaParams newRa) { + RaParams newlyDeprecated = new RaParams(); + + if (oldRa != null) { + for (IpPrefix ipp : oldRa.prefixes) { + if (newRa == null || !newRa.prefixes.contains(ipp)) { + newlyDeprecated.prefixes.add(ipp); + } + } + + for (Inet6Address dns : oldRa.dnses) { + if (newRa == null || !newRa.dnses.contains(dns)) { + newlyDeprecated.dnses.add(dns); + } + } + } + + return newlyDeprecated; + } + } + + private static class DeprecatedInfoTracker { + private final HashMap mPrefixes = new HashMap<>(); + private final HashMap mDnses = new HashMap<>(); + + Set getPrefixes() { return mPrefixes.keySet(); } + + void putPrefixes(Set prefixes) { + for (IpPrefix ipp : prefixes) { + mPrefixes.put(ipp, MAX_URGENT_RTR_ADVERTISEMENTS); + } + } + + void removePrefixes(Set prefixes) { + for (IpPrefix ipp : prefixes) { + mPrefixes.remove(ipp); + } + } + + Set getDnses() { return mDnses.keySet(); } + + void putDnses(Set dnses) { + for (Inet6Address dns : dnses) { + mDnses.put(dns, MAX_URGENT_RTR_ADVERTISEMENTS); + } + } + + void removeDnses(Set dnses) { + for (Inet6Address dns : dnses) { + mDnses.remove(dns); + } + } + + boolean isEmpty() { return mPrefixes.isEmpty() && mDnses.isEmpty(); } + + private boolean decrementCounters() { + boolean removed = decrementCounter(mPrefixes); + removed |= decrementCounter(mDnses); + return removed; + } + + private boolean decrementCounter(HashMap map) { + boolean removed = false; + + for (Iterator> it = map.entrySet().iterator(); + it.hasNext();) { + Map.Entry kv = it.next(); + if (kv.getValue() == 0) { + it.remove(); + removed = true; + } else { + kv.setValue(kv.getValue() - 1); + } + } + + return removed; + } } @@ -148,29 +226,24 @@ public class RouterAdvertisementDaemon { mIfIndex = ifindex; mHwAddr = hwaddr; mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mIfIndex), 0); - mDeprecatedPrefixes = new HashMap<>(); + mDeprecatedInfoTracker = new DeprecatedInfoTracker(); } - public void buildNewRa(RaParams params, HashSet newlyDeprecated) { - if (newlyDeprecated != null) { - synchronized (mLock) { - for (IpPrefix ipp : newlyDeprecated) { - mDeprecatedPrefixes.put(ipp, MAX_URGENT_RTR_ADVERTISEMENTS); - } - } - } - - // TODO: Send MAX_URGENT_RTR_ADVERTISEMENTS zero router lifetime RAs, - // iff. we have already sent an RA. - if (params == null || params.prefixes.isEmpty()) { - // No RA to be served at this time. - clearRa(); - return; - } - + public void buildNewRa(RaParams deprecatedParams, RaParams newParams) { synchronized (mLock) { - mRaParams = params; - assembleRa(); + if (deprecatedParams != null) { + mDeprecatedInfoTracker.putPrefixes(deprecatedParams.prefixes); + mDeprecatedInfoTracker.putDnses(deprecatedParams.dnses); + } + + if (newParams != null) { + // Process information that is no longer deprecated. + mDeprecatedInfoTracker.removePrefixes(newParams.prefixes); + mDeprecatedInfoTracker.removeDnses(newParams.dnses); + } + + mRaParams = newParams; + assembleRaLocked(); } maybeNotifyMulticastTransmitter(); @@ -196,73 +269,64 @@ public class RouterAdvertisementDaemon { mUnicastResponder = null; } - private void clearRa() { - boolean notifySocket; - synchronized (mLock) { - notifySocket = (mRaLength != 0); - mRaLength = 0; - } - if (notifySocket) { - maybeNotifyMulticastTransmitter(); - } - } - - private void assembleRa() { + private void assembleRaLocked() { final ByteBuffer ra = ByteBuffer.wrap(mRA); ra.order(ByteOrder.BIG_ENDIAN); - synchronized (mLock) { - try { - putHeader(ra, mRaParams.hasDefaultRoute); + boolean shouldSendRA = false; - putSlla(ra, mHwAddr); + try { + putHeader(ra, mRaParams != null && mRaParams.hasDefaultRoute); + putSlla(ra, mHwAddr); + mRaLength = ra.position(); - // https://tools.ietf.org/html/rfc5175#section-4 says: - // - // "MUST NOT be added to a Router Advertisement message - // if no flags in the option are set." - // - // putExpandedFlagsOption(ra); + // https://tools.ietf.org/html/rfc5175#section-4 says: + // + // "MUST NOT be added to a Router Advertisement message + // if no flags in the option are set." + // + // putExpandedFlagsOption(ra); + if (mRaParams != null) { putMtu(ra, mRaParams.mtu); + mRaLength = ra.position(); for (IpPrefix ipp : mRaParams.prefixes) { putPio(ra, ipp, DEFAULT_LIFETIME, DEFAULT_LIFETIME); - mDeprecatedPrefixes.remove(ipp); - } - - for (IpPrefix ipp : mDeprecatedPrefixes.keySet()) { - putPio(ra, ipp, 0, 0); + mRaLength = ra.position(); + shouldSendRA = true; } if (mRaParams.dnses.size() > 0) { - putRdnss(ra, mRaParams.dnses); + putRdnss(ra, mRaParams.dnses, DEFAULT_LIFETIME); + mRaLength = ra.position(); + shouldSendRA = true; } + } + for (IpPrefix ipp : mDeprecatedInfoTracker.getPrefixes()) { + putPio(ra, ipp, 0, 0); mRaLength = ra.position(); - } catch (BufferOverflowException e) { - Log.e(TAG, "Could not construct new RA: " + e); - mRaLength = 0; - return; + shouldSendRA = true; } - } - } - private int decrementDeprecatedPrefixes() { - int removed = 0; - - synchronized (mLock) { - for (Map.Entry kv : mDeprecatedPrefixes.entrySet()) { - if (kv.getValue() == 0) { - mDeprecatedPrefixes.remove(kv.getKey()); - removed++; - } else { - kv.setValue(kv.getValue() - 1); - } + final Set deprecatedDnses = mDeprecatedInfoTracker.getDnses(); + if (!deprecatedDnses.isEmpty()) { + putRdnss(ra, deprecatedDnses, 0); + mRaLength = ra.position(); + shouldSendRA = true; } + } catch (BufferOverflowException e) { + // The packet up to mRaLength is valid, since it has been updated + // progressively as the RA was built. Log an error, and continue + // on as best as possible. + Log.e(TAG, "Could not construct new RA: " + e); } - return removed; + // We have nothing worth announcing; indicate as much to maybeSendRA(). + if (!shouldSendRA) { + mRaLength = 0; + } } private void maybeNotifyMulticastTransmitter() { @@ -461,7 +525,7 @@ public class RouterAdvertisementDaemon { } } - private static void putRdnss(ByteBuffer ra, Set dnses) { + private static void putRdnss(ByteBuffer ra, Set dnses, int lifetime) { /** Recursive DNS Server (RDNSS) Option @@ -483,9 +547,15 @@ public class RouterAdvertisementDaemon { ra.put(ND_OPTION_RDNSS) .put(RDNSS_NUM_8OCTETS) .putShort(asShort(0)) - .putInt(DEFAULT_LIFETIME); + .putInt(lifetime); for (Inet6Address dns : dnses) { + // NOTE: If the full of list DNS servers doesn't fit in the packet, + // this code will cause a buffer overflow and the RA won't include + // include this instance of the option at all. + // + // TODO: Consider looking at ra.remaining() to determine how many + // DNS servers will fit, and adding only those. ra.put(dns.getAddress()); } } @@ -601,10 +671,12 @@ public class RouterAdvertisementDaemon { } maybeSendRA(mAllNodes); - if (decrementDeprecatedPrefixes() > 0) { - // At least one deprecated PIO has been removed; - // reassemble the RA. - assembleRa(); + synchronized (mLock) { + if (mDeprecatedInfoTracker.decrementCounters()) { + // At least one deprecated PIO has been removed; + // reassemble the RA. + assembleRaLocked(); + } } } } @@ -619,17 +691,17 @@ public class RouterAdvertisementDaemon { } private int getNextMulticastTransmitDelaySec() { - int countDeprecatedPrefixes = 0; + boolean deprecationInProgress = false; synchronized (mLock) { if (mRaLength < MIN_RA_HEADER_SIZE) { // No actual RA to send; just sleep for 1 day. return DAY_IN_SECONDS; } - countDeprecatedPrefixes = mDeprecatedPrefixes.size(); + deprecationInProgress = !mDeprecatedInfoTracker.isEmpty(); } final int urgentPending = mUrgentAnnouncements.getAndDecrement(); - if (urgentPending > 0 || countDeprecatedPrefixes > 0) { + if ((urgentPending > 0) || deprecationInProgress) { return MIN_DELAY_BETWEEN_RAS_SEC; }