Merge "Pass all offload-exempt prefixes into OffloadController"

This commit is contained in:
Treehugger Robot
2017-07-03 08:30:30 +00:00
committed by Gerrit Code Review
4 changed files with 386 additions and 178 deletions

View File

@@ -47,6 +47,7 @@ import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.INetworkPolicyManager; import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService; import android.net.INetworkStatsService;
import android.net.IpPrefix;
import android.net.LinkProperties; import android.net.LinkProperties;
import android.net.Network; import android.net.Network;
import android.net.NetworkCapabilities; import android.net.NetworkCapabilities;
@@ -104,6 +105,7 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@@ -210,7 +212,7 @@ public class Tethering extends BaseNetworkObserver {
mContext.getContentResolver(), mContext.getContentResolver(),
mLog); mLog);
mUpstreamNetworkMonitor = new UpstreamNetworkMonitor( mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
mContext, mTetherMasterSM, TetherMasterSM.EVENT_UPSTREAM_CALLBACK, mLog); mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK );
mForwardedDownstreams = new HashSet<>(); mForwardedDownstreams = new HashSet<>();
mSimChange = new SimChangeListener( mSimChange = new SimChangeListener(
mContext, mTetherMasterSM.getHandler(), () -> reevaluateSimCardProvisioning()); mContext, mTetherMasterSM.getHandler(), () -> reevaluateSimCardProvisioning());
@@ -1066,7 +1068,7 @@ public class Tethering extends BaseNetworkObserver {
// Needed because the canonical source of upstream truth is just the // Needed because the canonical source of upstream truth is just the
// upstream interface name, |mCurrentUpstreamIface|. This is ripe for // upstream interface name, |mCurrentUpstreamIface|. This is ripe for
// future simplification, once the upstream Network is canonical. // future simplification, once the upstream Network is canonical.
boolean pertainsToCurrentUpstream(NetworkState ns) { private boolean pertainsToCurrentUpstream(NetworkState ns) {
if (ns != null && ns.linkProperties != null && mCurrentUpstreamIface != null) { if (ns != null && ns.linkProperties != null && mCurrentUpstreamIface != null) {
for (String ifname : ns.linkProperties.getAllInterfaceNames()) { for (String ifname : ns.linkProperties.getAllInterfaceNames()) {
if (mCurrentUpstreamIface.equals(ifname)) { if (mCurrentUpstreamIface.equals(ifname)) {
@@ -1100,6 +1102,12 @@ public class Tethering extends BaseNetworkObserver {
} }
} }
private void startOffloadController() {
mOffloadController.start();
mOffloadController.updateExemptPrefixes(
mUpstreamNetworkMonitor.getOffloadExemptPrefixes());
}
class TetherMasterSM extends StateMachine { class TetherMasterSM extends StateMachine {
private static final int BASE_MASTER = Protocol.BASE_TETHERING; private static final int BASE_MASTER = Protocol.BASE_TETHERING;
// an interface SM has requested Tethering/Local Hotspot // an interface SM has requested Tethering/Local Hotspot
@@ -1193,146 +1201,138 @@ public class Tethering extends BaseNetworkObserver {
} }
} }
class TetherMasterUtilState extends State { protected boolean turnOnMasterTetherSettings() {
@Override final TetheringConfiguration cfg = mConfig;
public boolean processMessage(Message m) { try {
mNMService.setIpForwardingEnabled(true);
} catch (Exception e) {
mLog.e(e);
transitionTo(mSetIpForwardingEnabledErrorState);
return false; return false;
} }
// TODO: Randomize DHCPv4 ranges, especially in hotspot mode.
protected boolean turnOnMasterTetherSettings() { try {
final TetheringConfiguration cfg = mConfig; // TODO: Find a more accurate method name (startDHCPv4()?).
try { mNMService.startTethering(cfg.dhcpRanges);
mNMService.setIpForwardingEnabled(true); } catch (Exception e) {
} catch (Exception e) {
mLog.e(e);
transitionTo(mSetIpForwardingEnabledErrorState);
return false;
}
// TODO: Randomize DHCPv4 ranges, especially in hotspot mode.
try {
// TODO: Find a more accurate method name (startDHCPv4()?).
mNMService.startTethering(cfg.dhcpRanges);
} catch (Exception e) {
try {
mNMService.stopTethering();
mNMService.startTethering(cfg.dhcpRanges);
} catch (Exception ee) {
mLog.e(ee);
transitionTo(mStartTetheringErrorState);
return false;
}
}
mLog.log("SET master tether settings: ON");
return true;
}
protected boolean turnOffMasterTetherSettings() {
try { try {
mNMService.stopTethering(); mNMService.stopTethering();
} catch (Exception e) { mNMService.startTethering(cfg.dhcpRanges);
mLog.e(e); } catch (Exception ee) {
transitionTo(mStopTetheringErrorState); mLog.e(ee);
transitionTo(mStartTetheringErrorState);
return false; return false;
} }
try {
mNMService.setIpForwardingEnabled(false);
} catch (Exception e) {
mLog.e(e);
transitionTo(mSetIpForwardingDisabledErrorState);
return false;
}
transitionTo(mInitialState);
mLog.log("SET master tether settings: OFF");
return true;
} }
mLog.log("SET master tether settings: ON");
return true;
}
protected void chooseUpstreamType(boolean tryCell) { protected boolean turnOffMasterTetherSettings() {
updateConfiguration(); // TODO - remove? try {
mNMService.stopTethering();
final NetworkState ns = mUpstreamNetworkMonitor.selectPreferredUpstreamType( } catch (Exception e) {
mConfig.preferredUpstreamIfaceTypes); mLog.e(e);
if (ns == null) { transitionTo(mStopTetheringErrorState);
if (tryCell) { return false;
mUpstreamNetworkMonitor.registerMobileNetworkRequest();
// We think mobile should be coming up; don't set a retry.
} else {
sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
}
}
setUpstreamNetwork(ns);
} }
try {
mNMService.setIpForwardingEnabled(false);
} catch (Exception e) {
mLog.e(e);
transitionTo(mSetIpForwardingDisabledErrorState);
return false;
}
transitionTo(mInitialState);
mLog.log("SET master tether settings: OFF");
return true;
}
protected void setUpstreamNetwork(NetworkState ns) { protected void chooseUpstreamType(boolean tryCell) {
String iface = null; updateConfiguration(); // TODO - remove?
if (ns != null && ns.linkProperties != null) {
// Find the interface with the default IPv4 route. It may be the
// interface described by linkProperties, or one of the interfaces
// stacked on top of it.
Log.i(TAG, "Finding IPv4 upstream interface on: " + ns.linkProperties);
RouteInfo ipv4Default = RouteInfo.selectBestRoute(
ns.linkProperties.getAllRoutes(), Inet4Address.ANY);
if (ipv4Default != null) {
iface = ipv4Default.getInterface();
Log.i(TAG, "Found interface " + ipv4Default.getInterface());
} else {
Log.i(TAG, "No IPv4 upstream interface, giving up.");
}
}
if (iface != null) { final NetworkState ns = mUpstreamNetworkMonitor.selectPreferredUpstreamType(
setDnsForwarders(ns.network, ns.linkProperties); mConfig.preferredUpstreamIfaceTypes);
if (ns == null) {
if (tryCell) {
mUpstreamNetworkMonitor.registerMobileNetworkRequest();
// We think mobile should be coming up; don't set a retry.
} else {
sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
} }
notifyTetheredOfNewUpstreamIface(iface); }
if (ns != null && pertainsToCurrentUpstream(ns)) { setUpstreamNetwork(ns);
// If we already have NetworkState for this network examine }
// it immediately, because there likely will be no second
// EVENT_ON_AVAILABLE (it was already received). protected void setUpstreamNetwork(NetworkState ns) {
handleNewUpstreamNetworkState(ns); String iface = null;
} else if (mCurrentUpstreamIface == null) { if (ns != null && ns.linkProperties != null) {
// There are no available upstream networks, or none that // Find the interface with the default IPv4 route. It may be the
// have an IPv4 default route (current metric for success). // interface described by linkProperties, or one of the interfaces
handleNewUpstreamNetworkState(null); // stacked on top of it.
Log.i(TAG, "Finding IPv4 upstream interface on: " + ns.linkProperties);
RouteInfo ipv4Default = RouteInfo.selectBestRoute(
ns.linkProperties.getAllRoutes(), Inet4Address.ANY);
if (ipv4Default != null) {
iface = ipv4Default.getInterface();
Log.i(TAG, "Found interface " + ipv4Default.getInterface());
} else {
Log.i(TAG, "No IPv4 upstream interface, giving up.");
} }
} }
protected void setDnsForwarders(final Network network, final LinkProperties lp) { if (iface != null) {
// TODO: Set v4 and/or v6 DNS per available connectivity. setDnsForwarders(ns.network, ns.linkProperties);
String[] dnsServers = mConfig.defaultIPv4DNS;
final Collection<InetAddress> dnses = lp.getDnsServers();
// TODO: Properly support the absence of DNS servers.
if (dnses != null && !dnses.isEmpty()) {
// TODO: remove this invocation of NetworkUtils.makeStrings().
dnsServers = NetworkUtils.makeStrings(dnses);
}
try {
mNMService.setDnsForwarders(network, dnsServers);
mLog.log(String.format(
"SET DNS forwarders: network=%s dnsServers=%s",
network, Arrays.toString(dnsServers)));
} catch (Exception e) {
// TODO: Investigate how this can fail and what exactly
// happens if/when such failures occur.
mLog.e("setting DNS forwarders failed, " + e);
transitionTo(mSetDnsForwardersErrorState);
}
} }
notifyTetheredOfNewUpstreamIface(iface);
if (ns != null && pertainsToCurrentUpstream(ns)) {
// If we already have NetworkState for this network examine
// 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);
}
}
protected void notifyTetheredOfNewUpstreamIface(String ifaceName) { protected void setDnsForwarders(final Network network, final LinkProperties lp) {
if (DBG) Log.d(TAG, "Notifying tethered with upstream=" + ifaceName); // TODO: Set v4 and/or v6 DNS per available connectivity.
mCurrentUpstreamIface = ifaceName; String[] dnsServers = mConfig.defaultIPv4DNS;
for (TetherInterfaceStateMachine sm : mNotifyList) { final Collection<InetAddress> dnses = lp.getDnsServers();
sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, // TODO: Properly support the absence of DNS servers.
ifaceName); if (dnses != null && !dnses.isEmpty()) {
} // TODO: remove this invocation of NetworkUtils.makeStrings().
dnsServers = NetworkUtils.makeStrings(dnses);
} }
try {
mNMService.setDnsForwarders(network, dnsServers);
mLog.log(String.format(
"SET DNS forwarders: network=%s dnsServers=%s",
network, Arrays.toString(dnsServers)));
} catch (Exception e) {
// TODO: Investigate how this can fail and what exactly
// happens if/when such failures occur.
mLog.e("setting DNS forwarders failed, " + e);
transitionTo(mSetDnsForwardersErrorState);
}
}
protected void handleNewUpstreamNetworkState(NetworkState ns) { protected void notifyTetheredOfNewUpstreamIface(String ifaceName) {
mIPv6TetheringCoordinator.updateUpstreamNetworkState(ns); if (DBG) Log.d(TAG, "Notifying tethered with upstream=" + ifaceName);
mOffloadController.setUpstreamLinkProperties( mCurrentUpstreamIface = ifaceName;
(ns != null) ? ns.linkProperties : null); for (TetherInterfaceStateMachine sm : mNotifyList) {
sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
ifaceName);
} }
} }
protected void handleNewUpstreamNetworkState(NetworkState ns) {
mIPv6TetheringCoordinator.updateUpstreamNetworkState(ns);
mOffloadController.setUpstreamLinkProperties((ns != null) ? ns.linkProperties : null);
}
private void handleInterfaceServingStateActive(int mode, TetherInterfaceStateMachine who) { private void handleInterfaceServingStateActive(int mode, TetherInterfaceStateMachine who) {
if (mNotifyList.indexOf(who) < 0) { if (mNotifyList.indexOf(who) < 0) {
mNotifyList.add(who); mNotifyList.add(who);
@@ -1379,7 +1379,61 @@ public class Tethering extends BaseNetworkObserver {
} }
} }
class TetherModeAliveState extends TetherMasterUtilState { private void handleUpstreamNetworkMonitorCallback(int arg1, Object o) {
if (arg1 == UpstreamNetworkMonitor.NOTIFY_EXEMPT_PREFIXES) {
mOffloadController.updateExemptPrefixes((Set<IpPrefix>) o);
return;
}
final NetworkState ns = (NetworkState) o;
if (ns == null || !pertainsToCurrentUpstream(ns)) {
// TODO: In future, this is where upstream evaluation and selection
// could be handled for notifications which include sufficient data.
// For example, after CONNECTIVITY_ACTION listening is removed, here
// is where we could observe a Wi-Fi network becoming available and
// passing validation.
if (mCurrentUpstreamIface == null) {
// If we have no upstream interface, try to run through upstream
// selection again. If, for example, IPv4 connectivity has shown up
// after IPv6 (e.g., 464xlat became available) we want the chance to
// notice and act accordingly.
chooseUpstreamType(false);
}
return;
}
switch (arg1) {
case UpstreamNetworkMonitor.EVENT_ON_AVAILABLE:
// The default network changed, or DUN connected
// before this callback was processed. Updates
// for the current NetworkCapabilities and
// LinkProperties have been requested (default
// request) or are being sent shortly (DUN). Do
// nothing until they arrive; if no updates
// arrive there's nothing to do.
break;
case UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES:
handleNewUpstreamNetworkState(ns);
break;
case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES:
setDnsForwarders(ns.network, ns.linkProperties);
handleNewUpstreamNetworkState(ns);
break;
case UpstreamNetworkMonitor.EVENT_ON_LOST:
// TODO: Re-evaluate possible upstreams. Currently upstream
// reevaluation is triggered via received CONNECTIVITY_ACTION
// broadcasts that result in being passed a
// TetherMasterSM.CMD_UPSTREAM_CHANGED.
handleNewUpstreamNetworkState(null);
break;
default:
mLog.e("Unknown arg1 value: " + arg1);
break;
}
}
class TetherModeAliveState extends State {
boolean mUpstreamWanted = false; boolean mUpstreamWanted = false;
boolean mTryCell = true; boolean mTryCell = true;
@@ -1397,7 +1451,7 @@ public class Tethering extends BaseNetworkObserver {
// TODO: De-duplicate with updateUpstreamWanted() below. // TODO: De-duplicate with updateUpstreamWanted() below.
if (upstreamWanted()) { if (upstreamWanted()) {
mUpstreamWanted = true; mUpstreamWanted = true;
mOffloadController.start(); startOffloadController();
chooseUpstreamType(true); chooseUpstreamType(true);
mTryCell = false; mTryCell = false;
} }
@@ -1417,7 +1471,7 @@ public class Tethering extends BaseNetworkObserver {
mUpstreamWanted = upstreamWanted(); mUpstreamWanted = upstreamWanted();
if (mUpstreamWanted != previousUpstreamWanted) { if (mUpstreamWanted != previousUpstreamWanted) {
if (mUpstreamWanted) { if (mUpstreamWanted) {
mOffloadController.start(); startOffloadController();
} else { } else {
mOffloadController.stop(); mOffloadController.stop();
} }
@@ -1497,52 +1551,8 @@ public class Tethering extends BaseNetworkObserver {
break; break;
case EVENT_UPSTREAM_CALLBACK: { case EVENT_UPSTREAM_CALLBACK: {
updateUpstreamWanted(); updateUpstreamWanted();
if (!mUpstreamWanted) break; if (mUpstreamWanted) {
handleUpstreamNetworkMonitorCallback(message.arg1, message.obj);
final NetworkState ns = (NetworkState) message.obj;
if (ns == null || !pertainsToCurrentUpstream(ns)) {
// TODO: In future, this is where upstream evaluation and selection
// could be handled for notifications which include sufficient data.
// For example, after CONNECTIVITY_ACTION listening is removed, here
// is where we could observe a Wi-Fi network becoming available and
// passing validation.
if (mCurrentUpstreamIface == null) {
// If we have no upstream interface, try to run through upstream
// selection again. If, for example, IPv4 connectivity has shown up
// after IPv6 (e.g., 464xlat became available) we want the chance to
// notice and act accordingly.
chooseUpstreamType(false);
}
break;
}
switch (message.arg1) {
case UpstreamNetworkMonitor.EVENT_ON_AVAILABLE:
// The default network changed, or DUN connected
// before this callback was processed. Updates
// for the current NetworkCapabilities and
// LinkProperties have been requested (default
// request) or are being sent shortly (DUN). Do
// nothing until they arrive; if no updates
// arrive there's nothing to do.
break;
case UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES:
handleNewUpstreamNetworkState(ns);
break;
case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES:
setDnsForwarders(ns.network, ns.linkProperties);
handleNewUpstreamNetworkState(ns);
break;
case UpstreamNetworkMonitor.EVENT_ON_LOST:
// TODO: Re-evaluate possible upstreams. Currently upstream
// reevaluation is triggered via received CONNECTIVITY_ACTION
// broadcasts that result in being passed a
// TetherMasterSM.CMD_UPSTREAM_CHANGED.
handleNewUpstreamNetworkState(null);
break;
default:
break;
} }
break; break;
} }

View File

@@ -19,6 +19,7 @@ package com.android.server.connectivity.tethering;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED; import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.net.IpPrefix;
import android.net.LinkProperties; import android.net.LinkProperties;
import android.net.RouteInfo; import android.net.RouteInfo;
import android.net.util.SharedLog; import android.net.util.SharedLog;
@@ -28,6 +29,7 @@ import android.provider.Settings;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Set;
/** /**
* A class to encapsulate the business logic of programming the tethering * A class to encapsulate the business logic of programming the tethering
@@ -45,6 +47,7 @@ public class OffloadController {
private boolean mConfigInitialized; private boolean mConfigInitialized;
private boolean mControlInitialized; private boolean mControlInitialized;
private LinkProperties mUpstreamLinkProperties; private LinkProperties mUpstreamLinkProperties;
private Set<IpPrefix> mExemptPrefixes;
public OffloadController(Handler h, OffloadHardwareInterface hwi, public OffloadController(Handler h, OffloadHardwareInterface hwi,
ContentResolver contentResolver, SharedLog log) { ContentResolver contentResolver, SharedLog log) {
@@ -108,6 +111,17 @@ public class OffloadController {
pushUpstreamParameters(); pushUpstreamParameters();
} }
public void updateExemptPrefixes(Set<IpPrefix> exemptPrefixes) {
if (!started()) return;
mExemptPrefixes = exemptPrefixes;
// TODO:
// - add IP addresses from all downstream link properties
// - add routes from all non-tethering downstream link properties
// - remove any 64share prefixes
// - push this to the HAL
}
public void notifyDownstreamLinkProperties(LinkProperties lp) { public void notifyDownstreamLinkProperties(LinkProperties lp) {
if (!started()) return; if (!started()) return;

View File

@@ -26,18 +26,24 @@ import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivityManager.NetworkCallback;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties; import android.net.LinkProperties;
import android.net.Network; import android.net.Network;
import android.net.NetworkCapabilities; import android.net.NetworkCapabilities;
import android.net.NetworkRequest; import android.net.NetworkRequest;
import android.net.NetworkState; import android.net.NetworkState;
import android.net.util.NetworkConstants;
import android.net.util.SharedLog; import android.net.util.SharedLog;
import android.util.Log; import android.util.Log;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.StateMachine; import com.android.internal.util.StateMachine;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
/** /**
@@ -66,10 +72,16 @@ public class UpstreamNetworkMonitor {
private static final boolean DBG = false; private static final boolean DBG = false;
private static final boolean VDBG = false; private static final boolean VDBG = false;
private static final IpPrefix[] MINIMUM_LOCAL_PREFIXES_SET = {
prefix("127.0.0.0/8"), prefix("169.254.0.0/16"),
prefix("::/3"), prefix("fe80::/64"), prefix("fc00::/7"), prefix("ff00::/8"),
};
public static final int EVENT_ON_AVAILABLE = 1; public static final int EVENT_ON_AVAILABLE = 1;
public static final int EVENT_ON_CAPABILITIES = 2; public static final int EVENT_ON_CAPABILITIES = 2;
public static final int EVENT_ON_LINKPROPERTIES = 3; public static final int EVENT_ON_LINKPROPERTIES = 3;
public static final int EVENT_ON_LOST = 4; public static final int EVENT_ON_LOST = 4;
public static final int NOTIFY_EXEMPT_PREFIXES = 10;
private static final int CALLBACK_LISTEN_ALL = 1; private static final int CALLBACK_LISTEN_ALL = 1;
private static final int CALLBACK_TRACK_DEFAULT = 2; private static final int CALLBACK_TRACK_DEFAULT = 2;
@@ -81,6 +93,7 @@ public class UpstreamNetworkMonitor {
private final Handler mHandler; private final Handler mHandler;
private final int mWhat; private final int mWhat;
private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>(); private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
private HashSet<IpPrefix> mOffloadExemptPrefixes;
private ConnectivityManager mCM; private ConnectivityManager mCM;
private NetworkCallback mListenAllCallback; private NetworkCallback mListenAllCallback;
private NetworkCallback mDefaultNetworkCallback; private NetworkCallback mDefaultNetworkCallback;
@@ -88,18 +101,19 @@ public class UpstreamNetworkMonitor {
private boolean mDunRequired; private boolean mDunRequired;
private Network mCurrentDefault; private Network mCurrentDefault;
public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what, SharedLog log) { public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, SharedLog log, int what) {
mContext = ctx; mContext = ctx;
mTarget = tgt; mTarget = tgt;
mHandler = mTarget.getHandler(); mHandler = mTarget.getHandler();
mWhat = what;
mLog = log.forSubComponent(TAG); mLog = log.forSubComponent(TAG);
mWhat = what;
mOffloadExemptPrefixes = allOffloadExemptPrefixes(mNetworkMap.values());
} }
@VisibleForTesting @VisibleForTesting
public UpstreamNetworkMonitor( public UpstreamNetworkMonitor(
StateMachine tgt, int what, ConnectivityManager cm, SharedLog log) { ConnectivityManager cm, StateMachine tgt, SharedLog log, int what) {
this(null, tgt, what, log); this((Context) null, tgt, log, what);
mCM = cm; mCM = cm;
} }
@@ -209,6 +223,10 @@ public class UpstreamNetworkMonitor {
return typeStatePair.ns; return typeStatePair.ns;
} }
public Set<IpPrefix> getOffloadExemptPrefixes() {
return (Set<IpPrefix>) mOffloadExemptPrefixes.clone();
}
private void handleAvailable(int callbackType, Network network) { private void handleAvailable(int callbackType, Network network) {
if (VDBG) Log.d(TAG, "EVENT_ON_AVAILABLE for " + network); if (VDBG) Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
@@ -342,6 +360,14 @@ public class UpstreamNetworkMonitor {
notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network)); notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network));
} }
private void recomputeOffloadExemptPrefixes() {
final HashSet<IpPrefix> exemptPrefixes = allOffloadExemptPrefixes(mNetworkMap.values());
if (!mOffloadExemptPrefixes.equals(exemptPrefixes)) {
mOffloadExemptPrefixes = exemptPrefixes;
notifyTarget(NOTIFY_EXEMPT_PREFIXES, exemptPrefixes.clone());
}
}
// Fetch (and cache) a ConnectivityManager only if and when we need one. // Fetch (and cache) a ConnectivityManager only if and when we need one.
private ConnectivityManager cm() { private ConnectivityManager cm() {
if (mCM == null) { if (mCM == null) {
@@ -376,6 +402,7 @@ public class UpstreamNetworkMonitor {
@Override @Override
public void onLinkPropertiesChanged(Network network, LinkProperties newLp) { public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
handleLinkProp(network, newLp); handleLinkProp(network, newLp);
recomputeOffloadExemptPrefixes();
} }
// TODO: Handle onNetworkSuspended(); // TODO: Handle onNetworkSuspended();
@@ -384,6 +411,7 @@ public class UpstreamNetworkMonitor {
@Override @Override
public void onLost(Network network) { public void onLost(Network network) {
handleLost(mCallbackType, network); handleLost(mCallbackType, network);
recomputeOffloadExemptPrefixes();
} }
} }
@@ -395,16 +423,16 @@ public class UpstreamNetworkMonitor {
notifyTarget(which, mNetworkMap.get(network)); notifyTarget(which, mNetworkMap.get(network));
} }
private void notifyTarget(int which, NetworkState netstate) { private void notifyTarget(int which, Object obj) {
mTarget.sendMessage(mWhat, which, 0, netstate); mTarget.sendMessage(mWhat, which, 0, obj);
} }
static private class TypeStatePair { private static class TypeStatePair {
public int type = TYPE_NONE; public int type = TYPE_NONE;
public NetworkState ns = null; public NetworkState ns = null;
} }
static private TypeStatePair findFirstAvailableUpstreamByType( private static TypeStatePair findFirstAvailableUpstreamByType(
Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes) { Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes) {
final TypeStatePair result = new TypeStatePair(); final TypeStatePair result = new TypeStatePair();
@@ -431,4 +459,36 @@ public class UpstreamNetworkMonitor {
return result; return result;
} }
private static HashSet<IpPrefix> allOffloadExemptPrefixes(Iterable<NetworkState> netStates) {
final HashSet<IpPrefix> prefixSet = new HashSet<>();
addDefaultLocalPrefixes(prefixSet);
for (NetworkState ns : netStates) {
addOffloadExemptPrefixes(prefixSet, ns.linkProperties);
}
return prefixSet;
}
private static void addDefaultLocalPrefixes(Set<IpPrefix> prefixSet) {
Collections.addAll(prefixSet, MINIMUM_LOCAL_PREFIXES_SET);
}
private static void addOffloadExemptPrefixes(Set<IpPrefix> prefixSet, LinkProperties lp) {
if (lp == null) return;
for (LinkAddress linkAddr : lp.getAllLinkAddresses()) {
prefixSet.add(new IpPrefix(linkAddr.getAddress(), linkAddr.getPrefixLength()));
}
// TODO: Consider adding other non-default routes associated with this
// network. Traffic to these destinations should perhaps not go through
// the Internet (upstream).
}
private static IpPrefix prefix(String prefixStr) {
return new IpPrefix(prefixStr);
}
} }

View File

@@ -44,6 +44,9 @@ import android.os.Message;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivityManager.NetworkCallback;
import android.net.IConnectivityManager; import android.net.IConnectivityManager;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network; import android.net.Network;
import android.net.NetworkCapabilities; import android.net.NetworkCapabilities;
import android.net.NetworkRequest; import android.net.NetworkRequest;
@@ -66,6 +69,7 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@@ -77,6 +81,9 @@ import java.util.Set;
public class UpstreamNetworkMonitorTest { public class UpstreamNetworkMonitorTest {
private static final int EVENT_UNM_UPDATE = 1; private static final int EVENT_UNM_UPDATE = 1;
private static final boolean INCLUDES = true;
private static final boolean EXCLUDES = false;
@Mock private Context mContext; @Mock private Context mContext;
@Mock private IConnectivityManager mCS; @Mock private IConnectivityManager mCS;
@Mock private SharedLog mLog; @Mock private SharedLog mLog;
@@ -94,7 +101,8 @@ public class UpstreamNetworkMonitorTest {
mCM = spy(new TestConnectivityManager(mContext, mCS)); mCM = spy(new TestConnectivityManager(mContext, mCS));
mSM = new TestStateMachine(); mSM = new TestStateMachine();
mUNM = new UpstreamNetworkMonitor(mSM, EVENT_UNM_UPDATE, (ConnectivityManager) mCM, mLog); mUNM = new UpstreamNetworkMonitor(
(ConnectivityManager) mCM, mSM, mLog, EVENT_UNM_UPDATE);
} }
@After public void tearDown() throws Exception { @After public void tearDown() throws Exception {
@@ -315,6 +323,101 @@ public class UpstreamNetworkMonitorTest {
assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
} }
@Test
public void testOffloadExemptPrefixes() throws Exception {
mUNM.start();
// [0] Test minimum set of exempt prefixes.
Set<IpPrefix> exempt = mUNM.getOffloadExemptPrefixes();
final String[] MINSET = {
"127.0.0.0/8", "169.254.0.0/16",
"::/3", "fe80::/64", "fc00::/7", "ff00::/8",
};
assertPrefixSet(exempt, INCLUDES, MINSET);
final Set<String> alreadySeen = new HashSet<>();
Collections.addAll(alreadySeen, MINSET);
assertEquals(alreadySeen.size(), exempt.size());
// [1] Pretend Wi-Fi connects.
final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
final LinkProperties wifiLp = new LinkProperties();
wifiLp.setInterfaceName("wlan0");
final String[] WIFI_ADDRS = {
"fe80::827a:bfff:fe6f:374d", "100.112.103.18",
"2001:db8:4:fd00:827a:bfff:fe6f:374d",
"2001:db8:4:fd00:6dea:325a:fdae:4ef4",
"fd6a:a640:60bf:e985::123", // ULA address for good measure.
};
for (String addrStr : WIFI_ADDRS) {
final String cidr = addrStr.contains(":") ? "/64" : "/20";
wifiLp.addLinkAddress(new LinkAddress(addrStr + cidr));
}
wifiAgent.fakeConnect();
wifiAgent.sendLinkProperties(wifiLp);
exempt = mUNM.getOffloadExemptPrefixes();
assertPrefixSet(exempt, INCLUDES, alreadySeen);
final String[] wifiLinkPrefixes = {
// Excludes link-local as that's already tested within MINSET.
"100.112.96.0/20", "2001:db8:4:fd00::/64", "fd6a:a640:60bf:e985::/64",
};
assertPrefixSet(exempt, INCLUDES, wifiLinkPrefixes);
Collections.addAll(alreadySeen, wifiLinkPrefixes);
assertEquals(alreadySeen.size(), exempt.size());
// [2] Pretend mobile connects.
final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
final LinkProperties cellLp = new LinkProperties();
cellLp.setInterfaceName("rmnet_data0");
final String[] CELL_ADDRS = {
"10.102.211.48", "2001:db8:0:1:b50e:70d9:10c9:433d",
};
for (String addrStr : CELL_ADDRS) {
final String cidr = addrStr.contains(":") ? "/64" : "/27";
cellLp.addLinkAddress(new LinkAddress(addrStr + cidr));
}
cellAgent.fakeConnect();
cellAgent.sendLinkProperties(cellLp);
exempt = mUNM.getOffloadExemptPrefixes();
assertPrefixSet(exempt, INCLUDES, alreadySeen);
final String[] cellLinkPrefixes = { "10.102.211.32/27", "2001:db8:0:1::/64" };
assertPrefixSet(exempt, INCLUDES, cellLinkPrefixes);
Collections.addAll(alreadySeen, cellLinkPrefixes);
assertEquals(alreadySeen.size(), exempt.size());
// [3] Pretend DUN connects.
final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
final LinkProperties dunLp = new LinkProperties();
dunLp.setInterfaceName("rmnet_data1");
final String[] DUN_ADDRS = {
"192.0.2.48", "2001:db8:1:2:b50e:70d9:10c9:433d",
};
for (String addrStr : DUN_ADDRS) {
final String cidr = addrStr.contains(":") ? "/64" : "/27";
cellLp.addLinkAddress(new LinkAddress(addrStr + cidr));
}
dunAgent.fakeConnect();
dunAgent.sendLinkProperties(dunLp);
exempt = mUNM.getOffloadExemptPrefixes();
assertPrefixSet(exempt, INCLUDES, alreadySeen);
final String[] dunLinkPrefixes = { "192.0.2.32/27", "2001:db8:1:2::/64" };
assertPrefixSet(exempt, INCLUDES, dunLinkPrefixes);
Collections.addAll(alreadySeen, dunLinkPrefixes);
assertEquals(alreadySeen.size(), exempt.size());
// [4] Pretend Wi-Fi disconnected. It's addresses/prefixes should no
// longer be included (should be properly removed).
wifiAgent.fakeDisconnect();
exempt = mUNM.getOffloadExemptPrefixes();
assertPrefixSet(exempt, INCLUDES, MINSET);
assertPrefixSet(exempt, EXCLUDES, wifiLinkPrefixes);
assertPrefixSet(exempt, INCLUDES, cellLinkPrefixes);
assertPrefixSet(exempt, INCLUDES, dunLinkPrefixes);
}
private void assertSatisfiesLegacyType(int legacyType, NetworkState ns) { private void assertSatisfiesLegacyType(int legacyType, NetworkState ns) {
if (legacyType == TYPE_NONE) { if (legacyType == TYPE_NONE) {
assertTrue(ns == null); assertTrue(ns == null);
@@ -476,6 +579,12 @@ public class UpstreamNetworkMonitorTest {
cb.onLost(networkId); cb.onLost(networkId);
} }
} }
public void sendLinkProperties(LinkProperties lp) {
for (NetworkCallback cb : cm.listening.keySet()) {
cb.onLinkPropertiesChanged(networkId, lp);
}
}
} }
public static class TestStateMachine extends StateMachine { public static class TestStateMachine extends StateMachine {
@@ -504,4 +613,19 @@ public class UpstreamNetworkMonitorTest {
static NetworkCapabilities copy(NetworkCapabilities nc) { static NetworkCapabilities copy(NetworkCapabilities nc) {
return new NetworkCapabilities(nc); return new NetworkCapabilities(nc);
} }
static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, String... expected) {
final Set<String> expectedSet = new HashSet<>();
Collections.addAll(expectedSet, expected);
assertPrefixSet(prefixes, expectation, expectedSet);
}
static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, Set<String> expected) {
for (String expectedPrefix : expected) {
final String errStr = expectation ? "did not find" : "found";
assertEquals(
String.format("Failed expectation: %s prefix: %s", errStr, expectedPrefix),
expectation, prefixes.contains(new IpPrefix(expectedPrefix)));
}
}
} }