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
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<RouteInfo> 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<RouteInfo> localRoutes = new ArrayList<RouteInfo>();
|
||||
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<IpPrefix> deprecatedPrefixes, HashSet<IpPrefix> newPrefixes) {
|
||||
// [1] Remove the routes that are deprecated.
|
||||
if (!deprecatedPrefixes.isEmpty()) {
|
||||
final ArrayList<RouteInfo> 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<IpPrefix> addedPrefixes = (HashSet) newPrefixes.clone();
|
||||
if (mLastRaParams != null) {
|
||||
addedPrefixes.removeAll(mLastRaParams.prefixes);
|
||||
}
|
||||
|
||||
private void updateLocalRoutes(ArrayList<RouteInfo> 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<RouteInfo> 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<IpPrefix> 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<Inet6Address> deprecatedDnses, HashSet<Inet6Address> 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<Inet6Address> 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<RouteInfo> getLocalRoutesFor(HashSet<IpPrefix> prefixes) {
|
||||
final ArrayList<RouteInfo> localRoutes = new ArrayList<RouteInfo>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<IpPrefix, Integer> 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<IpPrefix, Integer> mPrefixes = new HashMap<>();
|
||||
private final HashMap<Inet6Address, Integer> mDnses = new HashMap<>();
|
||||
|
||||
Set<IpPrefix> getPrefixes() { return mPrefixes.keySet(); }
|
||||
|
||||
void putPrefixes(Set<IpPrefix> prefixes) {
|
||||
for (IpPrefix ipp : prefixes) {
|
||||
mPrefixes.put(ipp, MAX_URGENT_RTR_ADVERTISEMENTS);
|
||||
}
|
||||
}
|
||||
|
||||
void removePrefixes(Set<IpPrefix> prefixes) {
|
||||
for (IpPrefix ipp : prefixes) {
|
||||
mPrefixes.remove(ipp);
|
||||
}
|
||||
}
|
||||
|
||||
Set<Inet6Address> getDnses() { return mDnses.keySet(); }
|
||||
|
||||
void putDnses(Set<Inet6Address> dnses) {
|
||||
for (Inet6Address dns : dnses) {
|
||||
mDnses.put(dns, MAX_URGENT_RTR_ADVERTISEMENTS);
|
||||
}
|
||||
}
|
||||
|
||||
void removeDnses(Set<Inet6Address> 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 <T> boolean decrementCounter(HashMap<T, Integer> map) {
|
||||
boolean removed = false;
|
||||
|
||||
for (Iterator<Map.Entry<T, Integer>> it = map.entrySet().iterator();
|
||||
it.hasNext();) {
|
||||
Map.Entry<T, Integer> 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<IpPrefix> 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<IpPrefix, Integer> kv : mDeprecatedPrefixes.entrySet()) {
|
||||
if (kv.getValue() == 0) {
|
||||
mDeprecatedPrefixes.remove(kv.getKey());
|
||||
removed++;
|
||||
} else {
|
||||
kv.setValue(kv.getValue() - 1);
|
||||
}
|
||||
final Set<Inet6Address> 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<Inet6Address> dnses) {
|
||||
private static void putRdnss(ByteBuffer ra, Set<Inet6Address> 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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user